Hey, pretty much the title. The function moves a part in a random direction, it works well with about 10 parts but once it starts doing 20 - 30 Parts it starts to mess up.
I believe its because of the many loops despite using break whenever the part is no longer there.
Not quite sure how to achieve the same effect but without loops.
Thanks in advanced!
function RandomMove(Part, Speed, DecisionMakingTime: number, forwardAllowed: boolean, directionAllowed: boolean)
local Map = workspace.Map:GetChildren()[1]
Part.PrimaryPart.AlignPosition.Position = Vector3.new(3, Map.Position.Y, Map.Position.Z)
local forward = 0
local direction = 0
local y_direction = 0
local z_direction = 0
local MoveCooldown = false
local max_y = Map.Size.Y / 2
local max_z = Map.Size.Z / 2
local Decide
local Move
Decide = coroutine.create(function()
while true do
if Part.PrimaryPart then
if forwardAllowed then
forward = math.random(1, 2)
end
if directionAllowed then
direction = math.random(1, 2)
end
task.wait()
task.wait(DecisionMakingTime)
else
break
end
end
end)
coroutine.resume(Decide)
Move = coroutine.create(function()
while true do
wait()
if Part.PrimaryPart then
local f = 0 -- forward
local t = 0 -- turn
if MoveCooldown == false then
if forward == 1 then -- forward
f = Speed
y_direction = y_direction + Speed
elseif forward == 2 then -- Backward
f = -Speed
y_direction = y_direction - Speed
end
if direction == 1 then -- Left
t = -Speed
z_direction = z_direction + Speed
elseif direction == 2 then -- right
t = Speed
z_direction = z_direction - Speed
end
end
if Part.PrimaryPart == nil then return end
local newPosition = Part.PrimaryPart.Position + Vector3.new(0, f, t)
if math.abs(newPosition.Y - Map.Position.Y) <= max_y and math.abs(newPosition.Z - Map.Position.Z) <= max_z then
Part.PrimaryPart.AlignPosition.Position = newPosition
end
else
break
end
end
end)
coroutine.resume(Move)
end
for i,v in pairs(workspace.MovingParts:GetChildren()) do
-- There's about 20 Moving Parts.
RandomMove(v, 10, 0.1, true, true)
end
Generally you want to avoid using while true loops and breaks, you should make use of the condition in the loop and do while Part.PrimaryPart do instead.
wait()
I see you used task.wait() in the 1st function so im not sure why you went for wait() in the 2nd function? On another note I believe task.wait() and RunService.Heartbeat:Wait() are practically the same although I would prefer heartbeat for an empty wait
This might be because of wait() throttling. Replace the wait() with task.wait() to avoid this issue.
Now, let’s talk about the while true do loop: the fact of the matter is that you’re starting two while true loops in separate coroutines for every part. This is a little awkward. To your credit, it’s not exactly easy to separate the while true do loop creation from the random move, since each part needs to keep track of its own state. What you need is a cohesive way for your behavior (what’s in the while true do loops) and your state (foward, direction, movecooldown, etc). to interact. What you have now fits that description, but it’s not very elegant. I think it would be more elegant to use object oriented programming here (if you’re not familiar with that, do read the link!).
Consider the following structure:
-- Moving part class; perhaps make a separate modulescript for this that returns MovingPart.
local MovingPart = {}
MovingPart.__index = MovingPart
function MovingPart.new(part)
local self = {
Part = part,
Speed = 10,
ForwardAllowed = true,
DirectionAllowed = true,
DecisionMakingTime = 0.1,
CanDecide = true,
_forward = 0,
_direction = 0,
_moveCooldown = false,
- -etc.
}
setmetatable(self, MovingPart)
return self
end
function MovingPart:Decide()
-- code that sets state
end
function MovingPart:Move()
-- code that moves the part according to state
end
-- Main script
local movingParts = {}
for _, part in workspace.MovingParts:GetChildren() do
table.insert(movingParts, MovingPart.new(part))
end
while true do
for i, movingPart in movingParts do
if not movingPart then -- In case any get destroyed
table.remove(movingParts, i)
continue
end
if movingPart.CanDecide then
movingPart:Decide()
end
movingPart:Move()
task.wait()
end
end
Note that the above is untested and incomplete; it’s just an idea to get you started.
I expanded upon your Oop!
Tested and working.
I’m not sure what the intended result was, but we have some random movement!
@OP, there was a plenty of unused code here too. MoveCooldown, z_direction & y_direction were calculated and not used in your code.
I would move away from while true do and look more into using RunService in the future too.
I hope this was helpful!
-- get runservice for heartbeat or renderstepped connections
-- easier than coroutine
local Run = game:GetService("RunService")
-- * assuming the map does not change, move map outside of the function, store it once.
-- cleans up your function
local Map = workspace.Map:WaitForChild("MapPart")
local max_x = Map.Size.X / 2
local max_y = Map.Size.Y / 2
local max_z = Map.Size.Z / 2
-- Moving part class
local MovingPart = {}
MovingPart.__index = MovingPart
function MovingPart.new(model, attributes)
attributes = attributes or {}
local self = {}
self.Model = model
self.PrimaryPart = model.PrimaryPart
self.Elapsed = 0
self.Speed = attributes["Speed"] or 10
self.DecisionTime = attributes["DecisionTime"] or 0.5
self.DontAllowForward = attributes["DontAllowForward"] or false
self.DontAllowDirection = attributes["DontAllowDirection"] or false
self.Forward = false
self.Direction = false
setmetatable(self, MovingPart)
return self
end
function MovingPart:Decide()
if (not self.DontAllowForward) then
self.Forward = math.random(0,1) == 0
end
if (not self.DontAllowDirection) then
self.Direction = math.random(0,1) == 0
end
end
function MovingPart:Move(dt)
-- multily speed by dt for consistant speeds across framerates
local speed = self.Speed * dt
local f = self.Forward and speed or -speed
local t = self.Direction and -speed or speed
local newPosition = self.Model:GetPivot().Position + Vector3.new(f, 0, t)
if math.abs(newPosition.X - Map.Position.X) <= max_x and math.abs(newPosition.Z - Map.Position.Z) <= max_z then
self.Model:PivotTo(CFrame.new(newPosition))
end
end
-- Main script
local AllMovingParts = {}
local random = Random.new()
for _, part in workspace.MovingParts:GetChildren() do
local movingPartClass = MovingPart.new(part,
{
Speed = random:NextNumber(1,20),
DecideTime = random:NextNumber(0.1,1)
}
)
table.insert(AllMovingParts, movingPartClass)
end
Run.Heartbeat:Connect(function(dt)
for i, class in AllMovingParts do
if (not class.Model) or (not class.PrimaryPart) then
continue
end
class.Elapsed += dt
if class.Elapsed > class.DecisionTime then
class.Elapsed -= class.DecisionTime
class:Decide()
end
class:Move(dt)
end
end)
For heartbeat, especially if you only want something to run once every n seconds, while true do loops work fine as a replacement. For renderstepped and presimulation, they do not. Note that these two are functionally very similar, in that they both run every frame during the heartbeat stage:
The only difference is that the while true do loop runs in the delayed threads section and the heartbeat one runs in the deferred threads section, and thus, each frame, what’s in the while true do loop runs before what’s connected to the heartbeat event. This is not a big enough difference to be generally useful, and there are better ways to dictate the order at which things run every frame than to rely on this. So, for all intents and purposes, they’re the same.
The reason we prefer RunService is because using RunService events gives us more control over when in the frame things are run. Need something run before rendering? Use :BindToRenderStep(), where you can even specify a priority value to determine when your code is run in relation to other code bound to render step, including roblox’s default player input and camera controls. Need something done before physics simulation? Use RunService.PreSimulation. Got something that can wait until after physics simulation? Use RunService.Heartbeat.
But since code in while true do task.wait() end loops runs in the heartbeat stage anyway, as long as you don’t need the code to happen before rendering or physics simulation, you can get away with using the loop, espeically if you want to specify a delay inside of the task.wait().