Client-Side NPC Collisions

I am using @ 5uphi method of creating lots (200+) client sided NPCs for performance: How to optimise hundreds of humanoids

I have deviated from his pattern by spawning random NPCs using CreateHumanoidModelFromDescription. However, the NPCs have a habit of bunching up and colliding despite setting all parts CanCollide to false.

Obviously if the NPCs were server-side I would use the PhysicsService and CollisionGroup, but CollisionGroup is apparently Server-side only.

-- Should set all parts to Non-Collide
local function setCollisionGroupRecursive(object)
	if object:IsA("BasePart") then
		object.CanCollide = false
		object.CanTouch = false
	end
	if object:IsA("MeshPart") then
		object.CanCollide = false
		object.CanTouch = false
	end
	for _, child in ipairs(object:GetChildren()) do
		setCollisionGroupRecursive(child)
	end
end

local function CreateEnemy(part)
-- Apply Random Character to Dummy	
	local randomCharacter = math.random(1000000, 33414743)
	local characterRig = game.Players:GetHumanoidDescriptionFromUserId(randomCharacter)
	local model = game.Players:CreateHumanoidModelFromDescription(characterRig, Enum.HumanoidRigType.R6)
	model:WaitForChild("Animate"):Destroy()
	model.Name = part.Name
	model.PrimaryPart.CFrame = part.CFrame
	
-- MAKE DUMMY NOT COLLIDABLE	
	setCollisionGroupRecursive(model)
	model.Torso.CanCollide = false
	model.Torso.CanTouch = false
	model.Torso.CanQuery = false
end

Looking at the created model in the Workspace, after spawning the NPC, R6 always have orsoCanCollide = true and R15 have LowerTorso & UpperTorso CanCollide = true, despite being set to false in the script.

Does anybody know of a solution as the bunching up of the NPCs looks awful.

OK, so I rear a bit more and found this article: Let me use collision groups on the client - #10 by AlreadyPro
I mis-understood the error in Studio, thinking I couldn’t access CollisionGroups client-side, which is wrong. You can’t CreateCollisionGroup client side.

The solution, CreateCollisionGroup server-side, then you can SetPartCollisionGroup the NPC when it is created, fixing the problem.

Server script

-- SERVICES
local runService = game:GetService("RunService")
local PhysicsService = game:GetService("PhysicsService")

-- Collision Groups - NPC group Defined here, but not used
local playerCollisionGroup = "Players"
local npcCollisionGroup = "NPCs"
PhysicsService:CreateCollisionGroup(playerCollisionGroup)
PhysicsService:CreateCollisionGroup(npcCollisionGroup)
PhysicsService:CollisionGroupSetCollidable(playerCollisionGroup, playerCollisionGroup, false)
PhysicsService:CollisionGroupSetCollidable(playerCollisionGroup, npcCollisionGroup, false)
PhysicsService:CollisionGroupSetCollidable(npcCollisionGroup, npcCollisionGroup, false)

Client script

local function setCollisionGroupRecursive(object)
	if object:IsA("BasePart") then
		PhysicsService:SetPartCollisionGroup(object, npcCollisionGroup)
	end
	for _, child in ipairs(object:GetChildren()) do
		setCollisionGroupRecursive(child)
	end
end