"Boids" Flocking Swarming Simulation Luau Algorithm - Library Animated Bird+Bats+Insects V.1.3 [Open Source] FREE MODEL

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.

Infinite Procedurally Generated Candyland [Open-Sourced Code] ONLY REQUIRES A MODEL LIBRARY - Resources / Community Resources - Developer Forum | Roblox

[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)

Eliza Chatbot Ported to Luau [Open-Source] - Resources / Community Resources - Developer Forum | Roblox

Working Pirate Ship Boat Model with Animated Anchor Physics [Open Source] Library - Resources / Community Resources - Developer Forum | Roblox

PS: I added a thing that changes the Swarms position over time
image
Magus Art game developer and resource creator | creating cross-platform 3D RPG Video games available for free | Patreon

52 Likes

I wonder if Craig Reynolds, the guy who created the algorithm, was from New York. The name of his algorithm certainly makes it likely.

11 Likes

IDK but I’m really happy with the code and I’m excited to share it. It’s the first example open sourced for ROBLOX. Their was some one other attempt but he never shared it.

I’ve been meaning to write this for a while so I’m glad I got it done. It goes nicely with my fish simulation. I’m going to publish iterations to this code. Proabably going to make examples of different flocks smaller flocks and the like.

This system is great to build off! You could probably add species and predators or make all sorts of different swarms.
Some main points are
More complex rules can be added, such as obstacle avoidance and goal seeking.
The boids framework is often used in computer graphics, providing realistic-looking representations of flocks of birds and other creatures, such as schools of fish or herds of animals. It was for instance used in the 1998 video game Half-Life for the flying bird-like creatures seen at the end of the game on Xen, named “boid” in the game files.

The Boids model can be used for direct control and stabilization of teams of simple unmanned ground vehicles (UGV)[6] or micro aerial vehicles (MAV)[7] in swarm robotics. For stabilization of heterogeneous UAV-UGV teams, the model was adapted for using onboard relative localization by Saska et al.[8]
–exerpt from the wikipedia article

9 Likes

I’m here to share an update for this model! I have updated to the most current version and added some new examples of this in action! I also converted it into a module.
I added Butterflies
image
Bumble Bees
image

I also added support for humanoid objects because I wanted to add pixies so I’m using my template humanoid which requires a library that is not open sourced

Also this flocking behavior can be converted into a pathing style function and it will effectively generate swarms say you want to create some zombies

6 Likes

Could you please provide some footage?
Ty

3 Likes

This looks really cool! The code also looks pretty good until around halfway through where it completely falls apart with inconsistent variables, weird spacing, and terrible formatting in general. I really suggest you fix it up a bit.

4 Likes

Theirs no issue with the code lol I fail to see the issue you see. Their are no “inconsistent” variables and the code works as intended. Thanks for the feedback always glad to hear real constructive criticisms on something I gave away for free.


Modular functions are in a table and other functions are called as local functions. This script runs very efficiently. I tested it in several iterations. It can run a swarm of 100 objects and uses minimal resouces.
The first version is listed in the parent post and the newest version is found in the published model.

3 Likes

I’m not talking about the functionality of the code, I’m talking about the organization and formatting.

For the first half of the code, the code is neatly indented and spaced apart, the variables names are all pascalCase, and lots of the code is explained with comments.


But then around at line 197 the code starts getting kind of ugly; notice how the code is only sometimes randomly indented, theres a mix between pascalCase variables and lowercase variables, the variables names are abbreviated, and the isnt spaced the best.



5 Likes

If you don’t like it you can use this online tool to format it automatically.
Lua Beautifier Online to beautify Lua code (codebeautify.org)
I’m not really big on indentation since this is Luau not a language where indentation actually matters.
In conclusion I do not care about your opinions about formatting especially in a coding language were it makes no functional difference and is rather a matter of preference and style choice.
I disable all of studios auto-indentation settings. You may not but it’s still preference and has nothing to do with the functionality or usefulness of the code I shared.

I shared this resource because I was looking for something similar in the past and found no resources on the subject.
It appears the only ones who want to share feedback are the ones who can think of something negative to say and the rest just take the free model.

Appreciation the feedback but it’s not sufficient to simply criticize formatting and style choices when I shared a Novel and useful resource that is an accurate implementation of the Boids Flocking Framework. The quality is great because I provided animated models and converted the code into a module. It works as intended and can be used on a variety of applications ranging from particle effects to swarms and even potentially a unit of navigating models moving together as a swarm like ants.

The Get Meta Table functions allow you to Dynamically change the variables of the module without editing any code. So those are the only variables you should be concerned with, considering their’s a read and write function for the variables I would further consider the concerns not urgent.

Also the functions at the end of the script are different execution methods. I kept them easy to build new functions it requires nothing in the model but a primary part. Although currently it is designed to write the position via a tween. You could instead return the positions and execute a pathing function. I also updated it to work with models in addition to parts.

3 Likes

I’d recomend putting the store link in bold, or putting the rest of the other links in Hide Details; I missed it due to it being at the top of the page

2 Likes

I understand I’ll be posting the final updates to this resource this evening. I’m currently implementing it into my Procedural world generator and will add features as needed and I’ll make sure to place the GetMetaTable and SetMetaTable functions to the top to improve readability and run the code through the Lua beautifier so the formatting is prettier.
Also I feel the top of the page is the most visible place to place the link.

4 Likes

I posted what is likely the Final update to this Resource! I have added an executable object called “SwarmFocus” you simply place the object and poof appears a random swarm! I have included the model I used to generate Pixies using my AI character generator.

I also tested this an it navigates around obstacles!

It adds nice ambience to the game. It would be really easy to make a game catching butterflies with a net.
Or even JellyFish

Here’s some inspiration

I have updated the model with non animated Jellyfish to inspire.

In the future might modularize the code more to it core components in a seperate module and different implementations in a seperate descendant module.

Also improved the readability and modularity of the code in the published model and added Dove animation

Another update I fixed the orientation of the birds so they face the direction they are moving and added a thing that randomizes the size of the bird.
I fixed this bug where it was going through objects and stuff I accidentally had this variable set to 0 and meant to be the opposite
image

Okay finally have debugged the code to make it not throw positions inside terrain with this function I had laying around I used for a previous project.

local function detecter(material)
for i,v in material do 
if material[i] then
if i~="Size" then
--if material[i][1] then
for t,o in material[i] do 
for c,p in material[i][t] do 
if material[i][t][c]~=Enum.Material.Water and material[i][t][c]~=Enum.Material.Air then
return true
end
end 
end
end
end
end
return false
end


local terrain=workspace.Terrain

local function CheckTerrain(GoalPosition)
local GoalPosition=CFrame.new(GoalPosition)
local region = Region3.new(GoalPosition.Position-Vector3.new(2,2,2),GoalPosition.Position+Vector3.new(2,2,2))
local material = terrain:ReadVoxels(region, 4)        
if  detecter(material) ==true then
repeat
GoalPosition=GoalPosition:ToWorldSpace(CFrame.new(0,2,0))
local region = Region3.new(GoalPosition.Position-Vector3.new(2,2,2),GoalPosition.Position+Vector3.new(2,2,2))
material = terrain:ReadVoxels(region, 4)            
until detecter(material) ==false
GoalPosition=GoalPosition:ToWorldSpace(CFrame.new(0,0,0))
end
return GoalPosition.Position
end```
3 Likes

This model has been updated to it most recent version!


I fixed a thing where the boids were navigating through terrain.

In addition I fixed a their orientation to have less weight on aligning with nearby boids and more facing towards where they are moving. Also added doves! Which are recolors of the crows

1 Like

I make one joke about the New York accent, and next thing I know, I am notified of every change…

2 Likes

my bad I was posting updates. The resource is completed now though. Tested and bug free

2 Likes

I have added A animated bat to this Open Source Boid Library in addition I have done some performance improvements that allow thousands of boids to be on a single game at once without using too many resources. Enjoy the boids share your use case would be cool to see =]

I also have added a Blowfly and Dragonfly rigs to this library.
image
image

Side notes! I have adjusted the behavior to have gradually changing flock formation so the boids spread out and regroup over time while also moving the entire flock

Any way of having an option where they sometimes attact ? Like the bats ? With attack time to every 40 seconds …

Perhaps option for like wayoonts , so you could set places for them to go ?

Thanks

How to use it? I’m new to roblox studio and really wan’t to have it in my so called game

1 Like

Their is a object called swarmfocus in the model that is an executable object that you can clone into the workspace.

image

The code to run a flock in a particular position is at its simplest this.

local pos=script.Parent.Parent.Position
local b=require(game.ServerStorage.BoidsLib.Boid)
b.Generate(pos)

I have updated and taken care of some bug fixes that caused some issues.