I’m making a system where, while the player holds down the E key, parts start forming a circle around the player. The problem is, I can’t figure out the math to calculate the angle of the new part based on the previous one — right now, each part is being created without referencing the last one. I basically need to fix the part that handles the angles. If anyone knows how to help, I’d really appreciate it!
--\\VARIABLES
local ratio = 7
local angle = 45
local firstPart : Part
spawnRemote.OnServerEvent:Connect(function(plr, timer, isFirst)
local part = Instance.new('Part')
part.Name = 'Part' .. timer
part.Anchored = true
part.Parent = partFolder
local char = plr.Character
local hrp : Part = char and char:FindFirstChild('HumanoidRootPart')
if isFirst then
part.CFrame = hrp.CFrame + hrp.CFrame.LookVector * ratio
part.CFrame = CFrame.lookAt(part.Position, part.Position + Vector3.new(0, 1, 0), (hrp.Position - part.Position).Unit)
firstPart = part
else
local vetorRelativo = firstPart.Position - hrp.Position
angle += -math.rad(20)
local x = ratio * math.cos(angle)
local z = ratio * math.sin(angle)
part.CFrame = hrp.CFrame * CFrame.new(x, hrp.CFrame.LookVector.Y, z)
part.CFrame = CFrame.lookAt(part.Position, part.Position + Vector3.new(0, 1, 0), (hrp.Position - part.Position).Unit)
end
--angle = (math.rad(45) * timer)
end)
I’m not sure exactly what you want here. You showed a picture of the error, I take it that’s not it.
You had the right idea until you started mixing degrees and radians.
LocalScript
--LocalScript in StarterPlayerScripts
local UserInputService = game:GetService("UserInputService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
local Workspace = game:GetService("Workspace")
local spawnRemote = ReplicatedStorage:FindFirstChild("spawnRemote") or Instance.new("RemoteEvent", ReplicatedStorage)
spawnRemote.Name = "spawnRemote"
local partFolder = Workspace:FindFirstChild("partFolder") or Instance.new("Folder", Workspace)
partFolder.Name = "partFolder"
local timer = 0
local holding = false
UserInputService.InputBegan:Connect(function(input)
if input.KeyCode == Enum.KeyCode.E then
holding = true
timer = 0
spawnRemote:FireServer(timer, true)
end
end)
UserInputService.InputEnded:Connect(function(input)
if input.KeyCode == Enum.KeyCode.E then
holding = false
end
end)
RunService.RenderStepped:Connect(function()
if holding then
timer += 1
spawnRemote:FireServer(timer, false)
end
end)
ServerScript
--ServerScript in ServerScriptService
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Workspace = game:GetService("Workspace")
local spawnRemote = ReplicatedStorage:FindFirstChild("spawnRemote") or Instance.new("RemoteEvent", ReplicatedStorage)
spawnRemote.Name = "spawnRemote"
local partFolder = Workspace:FindFirstChild("partFolder") or Instance.new("Folder", Workspace)
partFolder.Name = "partFolder"
local ratio = 7
local angleStep = math.rad(20)
local firstPart
local currentAngle = 0
spawnRemote.OnServerEvent:Connect(function(plr, timer, isFirst)
local part = Instance.new('Part')
part.Name = 'Part' .. timer
part.Anchored = true
part.Size = Vector3.new(1,1,1)
part.Parent = partFolder
local hrp = plr.Character and plr.Character:FindFirstChild('HumanoidRootPart')
if not hrp then return end
if isFirst then
part.CFrame = hrp.CFrame * CFrame.new(ratio, 0, 0)
firstPart = part
currentAngle = 0
else
currentAngle += angleStep
local x = ratio * math.cos(currentAngle)
local z = ratio * math.sin(currentAngle)
part.CFrame = hrp.CFrame * CFrame.new(x, 0, z)
end
end)
You have to be standing totally still when you press the E.. I’m thinking that will be the next issue.
Well, that was certainly more of a challenge.
LocalScript
--LocalScript in StarterPlayerScripts
local UserInputService = game:GetService("UserInputService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
local Workspace = game:GetService("Workspace")
local spawnRemote = ReplicatedStorage:FindFirstChild("spawnRemote") or Instance.new("RemoteEvent", ReplicatedStorage)
spawnRemote.Name = "spawnRemote"
local partFolder = Workspace:FindFirstChild("partFolder") or Instance.new("Folder", Workspace)
partFolder.Name = "partFolder"
local timer = 0
local holding = false
local originCFrame
UserInputService.InputBegan:Connect(function(input)
if input.KeyCode == Enum.KeyCode.E then
local hrp = game.Players.LocalPlayer.Character and game.Players.LocalPlayer.Character:FindFirstChild("HumanoidRootPart")
if not hrp then return end
originCFrame = hrp.CFrame
holding = true
timer = 0
spawnRemote:FireServer(timer, true, originCFrame)
end
end)
UserInputService.InputEnded:Connect(function(input)
if input.KeyCode == Enum.KeyCode.E then
holding = false
end
end)
RunService.RenderStepped:Connect(function()
if holding then
timer += 1
spawnRemote:FireServer(timer, false, originCFrame)
end
end)
ServerScript
--ServerScript in ServerScriptService
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Workspace = game:GetService("Workspace")
local spawnRemote = ReplicatedStorage:FindFirstChild("spawnRemote") or Instance.new("RemoteEvent", ReplicatedStorage)
spawnRemote.Name = "spawnRemote"
local partFolder = Workspace:FindFirstChild("partFolder") or Instance.new("Folder", Workspace)
partFolder.Name = "partFolder"
local ratio = 7
local angleStep = math.rad(20)
local sequences = {}
spawnRemote.OnServerEvent:Connect(function(plr, timer, isFirst, originCFrame)
if isFirst then
sequences[plr] = {angle = 0, origin = originCFrame}
end
local seq = sequences[plr]
if not seq then return end
local part = Instance.new("Part")
part.Name = "Part" .. timer
part.Anchored = true
part.Size = Vector3.new(1,1,1)
part.Parent = partFolder
local x = ratio * math.cos(seq.angle)
local z = ratio * math.sin(seq.angle)
part.CFrame = seq.origin * CFrame.new(x, 0, z)
seq.angle += angleStep
end)
The problem is, the parts should spawn close to each other, since I want them to form a circle around the player while they’re holding E.
The first part spawns in the player’s LookVector, and the next parts should be generated side by side to form a circle. However, as you can see in the previous image, the second part spawned far away from the first one.
obs: The script is working, but not the way I want.
You need to calculate angleStep instead of using constant one.
To do that I added: partSize variable.
Also you need to orient parts toward player position.
--ServerScript in ServerScriptService
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Workspace = game:GetService("Workspace")
local spawnRemote = ReplicatedStorage:FindFirstChild("spawnRemote") or Instance.new("RemoteEvent", ReplicatedStorage)
spawnRemote.Name = "spawnRemote"
local partFolder = Workspace:FindFirstChild("partFolder") or Instance.new("Folder", Workspace)
partFolder.Name = "partFolder"
local ratio = 20
local partSize = Vector3.new(4, 3, 1)
-- calculate angle step based on part size and ratio
local angleStep = partSize.X / ratio
local sequences = {}
spawnRemote.OnServerEvent:Connect(function(plr, timer, isFirst, originCFrame)
if isFirst then
sequences[plr] = {angle = 0, origin = originCFrame}
end
local seq = sequences[plr]
if not seq then return end
local part = Instance.new("Part")
part.Name = "Part" .. timer
part.Anchored = true
part.Size = partSize
part.Parent = partFolder
local x = ratio * math.cos(seq.angle)
local z = ratio * math.sin(seq.angle)
part.CFrame = CFrame.lookAt((seq.origin * CFrame.new(x, 0, z)).Position, seq.origin.Position)
seq.angle += angleStep
-- stop on full circle
if seq.angle > math.pi * 2 then
sequences[plr] = nil
end
end)
First of all: thanks for the scripts!
But you should not do it on client side because of possible race condition between client and server that might result in two created events with the same name.
Client should wait for the event only: local spawnRemote = ReplicatedStorage:WaitForChild("spawnRemote")
Thanks!
I do that expecting it to be removed and set up manually. It’s not like anything like that would ever end up in a finished script I’d use. It’s both a script and an explanation of what needs to be set up at the same time. Those scripts are rough and just smashed together to reach concept validation. After that, you go back and refine them with proper scripting.
That should look like this in the end..
local remote = rep:WaitForChild("spawnRemote")
Come on now, this part is even more odd.
RunService.RenderStepped:Connect(function()
if holding then
timer += 1
spawnRemote:FireServer(timer, false, originCFrame)
end
end)
This silly stuff is only for quick testing. I just want to get here:
spawnRemote.OnServerEvent:Connect
So I can test that approach and get something rolling to work with. There is a method to the madness, after refining you end up with a sweet script like yours.
I agree.
This has one flaw that build speed depends of clients performance: faster clients with more fps build the wall faster, that obviously makes the game not balanced.
Sorry for not pointing into all odds.
I’ve got your point about concepts.
I am just a fan of race conditions and creation of spawnEvent has caught my eye.
Thank you for your scripts again, sir - much appreciated that!
You need to rotate additionally using model’s current look vector.
I’ve changed local script to use model’s pivot instead of humanoid root part’s one.
I’ve changed server script to use max parts count, this makes wall width variable but avoid walls overlapping.
local script
--LocalScript in StarterPlayerScripts
local UserInputService = game:GetService("UserInputService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
local Workspace = game:GetService("Workspace")
local spawnRemote = ReplicatedStorage:WaitForChild("spawnRemote")
local partFolder = Workspace:FindFirstChild("partFolder") or Instance.new("Folder", Workspace)
partFolder.Name = "partFolder"
local timer = 0
local holding = false
local originCFrame
UserInputService.InputBegan:Connect(function(input)
if input.KeyCode == Enum.KeyCode.E then
originCFrame = game.Players.LocalPlayer.Character:GetPivot()
holding = true
timer = 0
spawnRemote:FireServer(timer, true, originCFrame)
end
end)
UserInputService.InputEnded:Connect(function(input)
if input.KeyCode == Enum.KeyCode.E then
holding = false
end
end)
RunService.RenderStepped:Connect(function()
if holding then
timer += 1
spawnRemote:FireServer(timer, false, originCFrame)
end
end)
server script
--ServerScript in ServerScriptService
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Workspace = game:GetService("Workspace")
local spawnRemote = ReplicatedStorage:FindFirstChild("spawnRemote") or Instance.new("RemoteEvent", ReplicatedStorage)
spawnRemote.Name = "spawnRemote"
local partFolder = Workspace:FindFirstChild("partFolder") or Instance.new("Folder", Workspace)
partFolder.Name = "partFolder"
local ratio = 8
local total_parts = 16
local partSize = Vector3.new(ratio * math.pi * 2 / total_parts, 3, 1)
-- calculate angle step based on part size and ratio
local angleStep = partSize.X / ratio
local sequences = {}
spawnRemote.OnServerEvent:Connect(function(plr, timer, isFirst, originCFrame : CFrame)
if isFirst then
sequences[plr] = {angle = 0, origin = originCFrame}
end
local seq = sequences[plr]
if not seq then return end
local part = Instance.new("Part")
part.Name = "Part" .. timer
part.Anchored = true
part.Size = partSize
part.Parent = partFolder
local x = ratio * math.cos(seq.angle)
local z = ratio * math.sin(seq.angle)
local angle = CFrame.new(seq.origin.LookVector) * CFrame.Angles(0, math.pi * .5, 0)
part.CFrame = CFrame.lookAt(seq.origin * angle:VectorToWorldSpace(Vector3.new(x, 0, z)), seq.origin.Position)
seq.angle += angleStep
-- stop on full circle
if seq.angle > math.pi * 2 then
sequences[plr] = nil
end
end)