-
What do you want to achieve?
I’ve recently come across the following youtube video: https://www.youtube.com/watch?v=2OR9IdAUiFQ . I found the frontline they used (the black line) very interesting and wanted to imitate it but couldn’t figure out how. -
What is the issue?
I’ve come up with a script that splits my line into nodes/segments then it detects whenever the units (blocks) move near the line and it adjusts accordingly. The issues with this script is that it is very janky, completely different from the goal of the line in the video and also it fails to create new segments/nodes so some nodes become super long and rotated. The final issue I have is that sometimes the script has a hard time figuring out the direction a block is moving from/started at, this leads to issues of sometimes the line moves towards the unit that is advancing and not away from it, and it will also sometimes lead to the line moving backwards with a unit when the unit retreats (the line should only adjust when a unit advances not retreats). -
What solutions have you tried so far? Did you look for solutions on the Creator Hub?
I have tried asking some friends but they couldn’t help me, and I did try look on the creator hub but nothing that helped came up for me. -
Extra Features That Are Missing:
As in the video you can see how when a unit is encircled it will automatically destroy, I want it to detect if a unit is encircled and if it is don’t destroy it, only destroy the line if there is no unit inside. And also the script is unable to create encirclements properly right now since the line is always connected. Finally, there is also the issue that the line ignores enemy units right now, so if there is a unit advancing and another unit on the opposite side, the line will just go past the opposite side’s unit.
Here is my current script for the line:
local Workspace = game:GetService("Workspace")
local RunService = game:GetService("RunService")
local FRONTLINE_NAME = "Frontline"
local BLOCKS_FOLDER_NAME = "Blocks"
local NODE_COUNT = 50
local NODE_SPACING = 2
local MAX_AFFECT_RADIUS = 20
local PUSH_SCALE = 0.25
local DAMPING = 0.95
local MAX_DISTANCE_FROM_BLOCK = 5
local original = Workspace:FindFirstChild(FRONTLINE_NAME)
local blocksFolder = Workspace:FindFirstChild(BLOCKS_FOLDER_NAME)
assert(original and blocksFolder, "Missing Frontline or Blocks")
local baseCFrame = original.CFrame
local nodeHeight = original.Size.Y
original:Destroy()
-- NODE CONTAINER
local nodeFolder = Instance.new("Folder")
nodeFolder.Name = "Frontline"
nodeFolder.Parent = Workspace
-- NODE STRUCTURE
local nodes = {}
for i = 0, NODE_COUNT - 1 do
local part = Instance.new("Part")
part.Size = Vector3.new(NODE_SPACING, nodeHeight, 1)
part.Anchored = true
part.CanCollide = false
part.Material = Enum.Material.SmoothPlastic
part.Color = Color3.new(0, 0, 0)
part.Parent = nodeFolder
table.insert(nodes, {
Part = part,
XOffset = 0,
ZOffset = 0,
XVelocity = 0,
ZVelocity = 0,
})
end
local function positionNodes()
local start = baseCFrame.Position - baseCFrame.RightVector * ((#nodes - 1) * NODE_SPACING / 2)
for i, node in ipairs(nodes) do
local basePos = start + baseCFrame.RightVector * ((i - 1) * NODE_SPACING)
local offset = Vector3.new(node.XOffset, 0, node.ZOffset)
local pos = basePos + offset
node.Part.Position = pos
end
for i = 1, #nodes - 1 do
local current = nodes[i]
local nextNode = nodes[i + 1]
local vec = (nextNode.Part.Position - current.Part.Position)
local dist = vec.Magnitude
current.Part.Size = Vector3.new(current.Part.Size.X, current.Part.Size.Y, dist)
current.Part.CFrame = CFrame.lookAt(current.Part.Position, nextNode.Part.Position) * CFrame.new(0, 0, -dist / 2)
end
-- LAST NODE LOOKS AT PREVIOUS
if #nodes > 1 then
local last = nodes[#nodes]
local prev = nodes[#nodes - 1]
local vec = (last.Part.Position - prev.Part.Position)
last.Part.Size = Vector3.new(last.Part.Size.X, last.Part.Size.Y, vec.Magnitude)
last.Part.CFrame = CFrame.lookAt(last.Part.Position, prev.Part.Position) * CFrame.new(0, 0, -vec.Magnitude / 2)
end
end
local lastPositions = {}
local function isPushingToward(nodePos, blockPos, velocity)
local toNode = (nodePos - blockPos).Unit
local speedTowardNode = velocity:Dot(toNode)
return speedTowardNode > 0, speedTowardNode
end
local FRICTION = 0.9
local MASS = 1 -- YOU CAN TUNE THIS TO MAKE IT MORE/LESS SLUGGISH
local MAX_PUSH_DISTANCE = 1 -- NODES WON'T GO CLOSER THAN THIS TO A PUSHING BLOCK
local NEW_NODE_DISTANCE = NODE_SPACING * 1.25 -- WHEN NEEDED, ADD A NEW NODE TO EXTEND THE LINE
local MAX_TOTAL_SPREAD = NODE_COUNT * NODE_SPACING * 2 -- PREVENT ARTIFICIAL LIMIT
-- OVERRIDE APPLYPUSHFORCES WITH THESE CHANGES:
local function applyPushForces()
local newPositions = {}
local blockVelocities = {}
-- CALCULATE BLOCK VELOCITIES
for _, block in ipairs(blocksFolder:GetChildren()) do
if block:IsA("BasePart") then
local last = lastPositions[block] or block.Position
local velocity = (block.Position - last)
blockVelocities[block] = velocity
newPositions[block] = block.Position
end
end
local totalWidth = (#nodes - 1) * NODE_SPACING
for i, node in ipairs(nodes) do
local nodePos = node.Part.Position
local forceX, forceZ = 0, 0
for block, velocity in pairs(blockVelocities) do
local blockPos = newPositions[block]
local planarDistVec = Vector3.new(blockPos.X, 0, blockPos.Z) - Vector3.new(nodePos.X, 0, nodePos.Z)
local planarDist = planarDistVec.Magnitude
if planarDist <= MAX_AFFECT_RADIUS then
local toNode = (nodePos - blockPos).Unit
local speedTowardNode = velocity:Dot(toNode)
-- ONLY PUSH IF MOVING TOWARD THE NODE
if speedTowardNode > 0 then
local influence = (1 - (planarDist / MAX_AFFECT_RADIUS))
-- DON'T PUSH NODE PAST THE BLOCK'S POSITION MINUS 1 STUD BUFFER
local futureOffset = Vector3.new(node.XOffset + node.XVelocity, 0, node.ZOffset + node.ZVelocity)
local futurePos = node.Part.Position + futureOffset
local stopVec = Vector3.new(blockPos.X, 0, blockPos.Z) - futurePos
if stopVec.Magnitude > MAX_PUSH_DISTANCE then
forceX += toNode.X * speedTowardNode * influence
forceZ += toNode.Z * speedTowardNode * influence
end
end
end
end
-- APPLY VELOCITY WITH DAMPING
node.XVelocity = (node.XVelocity + (forceX / MASS)) * FRICTION
node.ZVelocity = (node.ZVelocity + (forceZ / MASS)) * FRICTION
-- UPDATE OFFSETS
node.XOffset += node.XVelocity
node.ZOffset += node.ZVelocity
end
lastPositions = newPositions
end
RunService.Heartbeat:Connect(function()
applyPushForces()
positionNodes()
end)
I would appreciate if someone could find out where the math in my script is going wrong, where and how I can add the size limit of the nodes (and then adding new nodes when needed) and also preventing the lines from going too far out and so they can detect enemy units.
Here is a gif (I’m using gyazo) of how the line currently behaves:
Here is the current studio file if you want the place I’m testing it in:
FrontlineTest.rbxl (58.7 KB)