Here is a example of Boids algorithm implemented in Lua!
You can read more about the original algorithm here.
Boids - Wikipedia
create.roblox.com/store/asset/16091841007
-- Define some constants for the boids parameters
local BOIDS_COUNT = 100 -- The number of boids to create
local BOIDS_RADIUS = 10 -- The radius of the boids sphere
local BOIDS_SPEED = 10 -- The maximum speed of the boids
local BOIDS_SEPARATION = 2 -- The minimum distance between boids
local BOIDS_ALIGNMENT = 1 -- The factor for aligning with nearby boids
local BOIDS_COHESION = 1 -- The factor for moving towards the center of mass
local BOIDS_AVOIDANCE = 10 -- The factor for avoiding obstacles
local Boundary=Vector3.new(400,400,400)
-- Get the workspace and the tween service
local Workspace = game:GetService("Workspace")
local TweenService = game:GetService("TweenService")
-- Create a folder to store the boids
local BoidsFolder = Instance.new("Folder")
BoidsFolder.Name = "Boids"
BoidsFolder.Parent = Workspace
-- Create a table to store the boids data
local Boids = {}
-- A local function to create a random vector
local function RandomVector()
return Vector3.new(math.random() * 2 - 1, math.random() * 2 - 1, math.random() * 2 - 1)
end
-- A local function to clamp a vector to a maximum length
local function ClampVector(v, max)
local len = v.Magnitude
if len > max then
return v / len * max
else
return v
end
end
-- A local function to get the distance between two vectors
local function Distance(v1, v2)
return (v1 - v2).Magnitude
end
-- A local function to get the center of mass of a table of vectors
local function CenterOfMass(vectors)
local sum = Vector3.new(0, 0, 0)
local count = #vectors
for i = 1, count do
sum = sum + vectors[i]
end
return sum / count
end
-- A local function to apply the boids rules to a boid
local function ApplyRules(boid)
-- Get the boid's position and velocity
local position = boid.Position
local velocity = boid.Velocity
-- Initialize some vectors for the rules
local separation = Vector3.new(0, 0, 0) -- The vector for moving away from nearby boids
local alignment = Vector3.new(0, 0, 0) -- The vector for aligning with nearby boids
local cohesion = Vector3.new(0, 0, 0) -- The vector for moving towards the center of mass
local avoidance = Vector3.new(0, 0, 0) -- The vector for avoiding obstacles
-- Initialize some counters for the rules
local separationCount = 0 -- The number of nearby boids
local alignmentCount = 0 -- The number of nearby boids
local cohesionCount = 0 -- The number of nearby boids
-- Loop through all the other boids
for i = 1, BOIDS_COUNT do
local other = Boids[i]
-- Skip the same boid
if other ~= boid then
-- Get the other boid's position and velocity
local otherPosition = other.Position
local otherVelocity = other.Velocity
-- Get the distance between the boids
local dist = Distance(position, otherPosition)
-- Apply the separation rule if the distance is less than the separation distance
if dist < BOIDS_SEPARATION then
separation = separation + (position - otherPosition) / dist -- Add the normalized vector pointing away from the other boid
separationCount = separationCount + 1 -- Increment the counter
end
-- Apply the alignment rule if the distance is less than the radius
if dist < BOIDS_RADIUS then
alignment = alignment + otherVelocity -- Add the other boid's velocity
alignmentCount = alignmentCount + 1 -- Increment the counter
end
-- Apply the cohesion rule if the distance is less than the radius
if dist < BOIDS_RADIUS then
cohesion = cohesion + otherPosition -- Add the other boid's position
cohesionCount = cohesionCount + 1 -- Increment the counter
end
end
end
-- Normalize and scale the separation vector if there are nearby boids
if separationCount > 0 then
separation = separation / separationCount -- Get the average vector
separation = separation.Unit * BOIDS_SPEED -- Scale to the maximum speed
separation = separation - velocity -- Subtract the current velocity
separation = ClampVector(separation, BOIDS_SEPARATION) -- Clamp to the separation factor
end
-- Normalize and scale the alignment vector if there are nearby boids
if alignmentCount > 0 then
alignment = alignment / alignmentCount -- Get the average vector
alignment = alignment.Unit * BOIDS_SPEED -- Scale to the maximum speed
alignment = alignment - velocity -- Subtract the current velocity
alignment = ClampVector(alignment, BOIDS_ALIGNMENT) -- Clamp to the alignment factor
end
-- Normalize and scale the cohesion vector if there are nearby boids
if cohesionCount > 0 then
cohesion = cohesion / cohesionCount -- Get the center of mass
cohesion = cohesion - position -- Get the vector pointing towards the center of mass
cohesion = cohesion.Unit * BOIDS_SPEED -- Scale to the maximum speed
cohesion = cohesion - velocity -- Subtract the current velocity
cohesion = ClampVector(cohesion, BOIDS_COHESION) -- Clamp to the cohesion factor
end
-- Apply the avoidance rule by casting rays in different directions and finding the closest obstacle
local directions = { -- The directions to cast rays
Vector3.new(1, 0, 0), -- Forward
Vector3.new(-1, 0, 0), -- Backward
Vector3.new(0, 1, 0), -- Up
Vector3.new(0, -1, 0), -- Down
Vector3.new(0, 0, 1), -- Right
Vector3.new(0, 0, -1), -- Left
}
local closestDist = math.huge -- The distance to the closest obstacle
local closestNormal = Vector3.new(0, 0, 0) -- The normal of the closest obstacle
for i = 1, #directions do
local dir = directions[i] -- Get the direction
local ray = Ray.new(position, dir * BOIDS_RADIUS) -- Create a ray
local part, point, normal = Workspace:FindPartOnRay(ray, BoidsFolder, true) -- Cast the ray and ignore the boids folder
if part then -- If an obstacle is found
local dist = Distance(position, point) -- Get the distance to the obstacle
if dist < closestDist then -- If the distance is less than the closest distance
closestDist = dist -- Update the closest distance
closestNormal = normal -- Update the closest normal
end
end
end
-- If an obstacle is found, calculate the avoidance vector
if closestDist < math.huge then
avoidance = closestNormal * BOIDS_SPEED -- Scale the normal to the maximum speed
avoidance = avoidance - velocity -- Subtract the current velocity
avoidance = ClampVector(avoidance, BOIDS_AVOIDANCE) -- Clamp to the avoidance factor
end
-- Return the sum of all the rule vectors
return separation + alignment + cohesion + avoidance
end
-- A local function to update a boid's position and velocity
local function UpdateBoid(boid,i)
-- Get the boid's position and velocity
local position = boid.Position
local velocity = boid.Velocity
-- Apply the boids rules and get the new velocity
local newVelocity = ApplyRules(boid)
-- Clamp the new velocity to the maximum speed
newVelocity = ClampVector(newVelocity, BOIDS_SPEED)
-- Update the boid's velocity
boid.Velocity = newVelocity
-- Calculate the new position by adding the new velocity
local newPosition = position + newVelocity
-- Wrap the new position around the workspace boundaries
if newPosition.X > Boundary.X / 2 then
newPosition = newPosition - Vector3.new(Boundary.X, 0, 0)
elseif newPosition.X < -Boundary.X / 2 then
newPosition = newPosition + Vector3.new(Boundary.X, 0, 0)
end
if newPosition.Y > Boundary.Y / 2 then
newPosition = newPosition - Vector3.new(0, Boundary.Y, 0)
elseif newPosition.Y < -Boundary.Y / 2 then
newPosition = newPosition + Vector3.new(0, Boundary.Y, 0)
end
if newPosition.Z > Boundary.Z / 2 then
newPosition = newPosition - Vector3.new(0, 0, Boundary.Z)
elseif newPosition.Z < -Boundary.Z / 2 then
newPosition = newPosition + Vector3.new(0, 0, Boundary.Z)
end
-- Create a tween to animate the boid's meshpart
local meshpart = boid.Meshpart
local newPosition2=meshpart.Position+(newVelocity/6)
local dif=(newPosition2-meshpart.Position)
local speed=dif.Magnitude/10*BOIDS_SPEED
local lookvect=(CFrame.new(meshpart.Position,newPosition2))
local tweenInfo = TweenInfo.new(speed) -- Create a tween info with a short duration
local goal = {} -- Create an empty table for the goal properties
goal.CFrame = CFrame.new(newPosition2,newPosition2+dif) -- Set the goal CFrame to the new position and orientation
local tween = TweenService:Create(meshpart, tweenInfo, goal) -- Create the tween
tween:Play() -- Play the tween
Boids[i].Debounce=true
task.delay(speed-0.05,function()
local dif=(newPosition-meshpart.Position)
local speed=dif.Magnitude/10*BOIDS_SPEED
--local lookvect=(CFrame.new(newPosition2,newPosition))
local tweenInfo = TweenInfo.new(speed) -- Create a tween info with a short duration
local goal = {} -- Create an empty table for the goal properties
goal.CFrame = CFrame.new(newPosition,newPosition+dif) -- Set the goal CFrame to the new position and orientation
local tween = TweenService:Create(meshpart, tweenInfo, goal) -- Create the tween
tween:Play()
task.wait(speed-.05)
Boids[i].Debounce=false end)
end
local function newbird(position,velocity,i)
local meshpart=script.MeshPart:Clone()
meshpart.Name = "Boid" .. i
meshpart.Size = Vector3.new(2.909, 1.326, 2.174)*(math.random(50,150)/100)
meshpart.CFrame = CFrame.new(position, position + velocity)
meshpart.Parent = BoidsFolder
return meshpart
end
-- A function to create and update the boids
local function Main()
-- Create the boids
for i = 1, BOIDS_COUNT do
-- Create a random position and velocity
local position = RandomVector() * BOIDS_RADIUS
local velocity = RandomVector() * BOIDS_SPEED
-- Create a meshpart for the boid
local meshpart = newbird(position,velocity,i)
-- Store the boid data in the table
Boids[i] = {
Position = position,
Velocity = velocity,
Meshpart = meshpart,
Debounce=false
}
end
-- Update the boids every frame
while true do
-- Loop through all the boids
for i = 1, BOIDS_COUNT do
-- Get the boid data
if Boids[i].Debounce==false then
local boid = Boids[i]
local position = RandomVector() * BOIDS_RADIUS
local velocity = RandomVector() * BOIDS_SPEED
Boids[i].Position = position
Boids[i].Velocity = velocity
-- Update the boid's position and velocity
UpdateBoid(boid,i)
end
end
-- Wait for the next frame
wait()
end
end
-- Call the main function
Main()
Finally here is the code that animates the birds!
local lwing=script.Parent["Left Wing"]
local rwing=script.Parent["Right Wing"]
local TweenService = game:GetService("TweenService")
local function initialframe()
lwing.C1=CFrame.new(lwing.C1.Position)* CFrame.new(0,math.rad(45),0)
rwing.C1=CFrame.new(rwing.C1.Position)* CFrame.new(0,math.rad(-45),0)
end
--initialframe()
local function endframe()
lwing.C1=CFrame.new(lwing.C1.Position)* CFrame.new(0,math.rad(-45),0)
rwing.C1=CFrame.new(rwing.C1.Position)* CFrame.new(0,math.rad(45),0)
end
local tweeninfo=TweenInfo.new(.3)
local function animate()
local lw=TweenService:Create(lwing,tweeninfo,{C1=CFrame.new(lwing.C1.Position)* CFrame.Angles(0,math.rad(-45),0)})
local rw=TweenService:Create(rwing,tweeninfo,{C1=CFrame.new(rwing.C1.Position)* CFrame.Angles(0,math.rad(45),0)})
rw:Play() lw:Play()
lw.Completed:Wait()
local lw=TweenService:Create(lwing,tweeninfo,{C1=CFrame.new(lwing.C1.Position)* CFrame.Angles(0,math.rad(45),0)})
local rw=TweenService:Create(rwing,tweeninfo,{C1=CFrame.new(rwing.C1.Position)* CFrame.Angles(0,math.rad(-45),0)})
rw:Play() lw:Play()
lw.Completed:Wait()
end
while true do
animate()
end
If you would like check out some of my other Open Sourced projects I shared.
[Resize(Model,ScaleVector) X,Y,Z Tween Emitters, Bones, Mesh, Motors, HipHeight, Lights,Tool.Grip, Textures,Fire,Smoke,Weld [Open Source] - Resources / Community Resources - Developer Forum | Roblox
(Resize Model Scale Vector X,Y,Z Tween Emitters, Bones, Mesh, Motors, HipHeight, Lights,Tool.Grip, Textures,Fire,Smoke,Weld [Open Source] - #2 by Magus_ArtStudios)
[Text-Vision Awareness LLM Utility Luau [OPEN SOURCE] now Judges Terrain, Water, Floor Material and More - Resources / Community Resources - Developer Forum | Roblox]
(Text-Vision Awareness LLM Utility Luau [OPEN SOURCE] now Judges Terrain, Water, Floor Material and More)
PS: I added a thing that changes the Swarms position over time
Magus Art game developer and resource creator | creating cross-platform 3D RPG Video games available for free | Patreon