PcoWSkbVqDnWTu_dm2ix
We use cookies on this site to enhance your user experience

Player-Player Collisions

Player-Player Collisions

Jul 26 2018, 1:46 PM PST 10 min

By default Roblox characters can collide with each other. This can lead to some interesting gameplay, such as characters jumping on top of each other to reach specific areas. But in some cases players running into each other is not desirable, and it would be useful to disable these collisions. In this tutorial we will cover how to disable collisions between characters (while still allowing characters to collide with the rest of the world).

To disable player-player collisions, we will need to create a Articles/Collision Filtering|collision group for the players. This collision group will be configured to not collide with itself. Parts in this group will still collide with other parts (normally part of the Default group), but if they encounter a Part in the same group, they will pass through each other.

We start off by making a variable for the PhysicsService and the name of our collision group.

local PhysicsService = game:GetService("PhysicsService")
local playerCollisionGroupName = "Players"

We can then create the collision group with CreateCollisionGroup and set it not to collide with itself by calling CollisionGroupSetCollidable.

local PhysicsService = game:GetService("PhysicsService")
local playerCollisionGroupName = "Players"
PhysicsService:CreateCollisionGroup(playerCollisionGroupName)
PhysicsService:CollisionGroupSetCollidable(playerCollisionGroupName, playerCollisionGroupName, false)

Now all we need to do is add the parts of player characters to this collision group. These parts won’t be present in the game to start, so we will need to set up some events so we know when we can add the character to the group.

local PhysicsService = game:GetService("PhysicsService")
local Players = game:GetService("Players")

local playerCollisionGroupName = "Players"
PhysicsService:CreateCollisionGroup(playerCollisionGroupName)
PhysicsService:CollisionGroupSetCollidable(playerCollisionGroupName, playerCollisionGroupName, false)

local function onCharacterAdded(character)
end

local function onPlayerAdded(player)
  player.CharacterAdded:Connect(onCharacterAdded)
end

Players.PlayerAdded:Connect(onPlayerAdded)

With the above code, we first bind onPlayerAdded to PlayerAdded, and then onCharacterAdded to CharacterAdded. Inside onCharacterAdded we can add code to add the character’s parts to our collision group.

Unfortunately, character models can have a complex structure, and we will need something more involved than looping over the children of the character model. For example, any accessories that a player is wearing will have a part inside of it. Even though those parts will often be CanCollide false, it is important to add them to our collision group anyway just in case.

The simplest way to go through the parts in a player character is to use a recursive function. This function will simply be passed an Instance. If this Instance is a BasePart, then we can set its collision group. After that, we go through all of the children of the object (whether it is a part or not), and call the recursive function on each of the children.

Once this recursive function is set up, we can call it from onCharacterAdded and call it on the character model itself.

local PhysicsService = game:GetService("PhysicsService")
local Players = game:GetService("Players")

local playerCollisionGroupName = "Players"
PhysicsService:CreateCollisionGroup(playerCollisionGroupName)
PhysicsService:CollisionGroupSetCollidable(playerCollisionGroupName, playerCollisionGroupName, false)

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

local function onCharacterAdded(character)
  setCollisionGroupRecursive(character)
end

local function onPlayerAdded(player)
  player.CharacterAdded:Connect(onCharacterAdded)
end

Players.PlayerAdded:Connect(onPlayerAdded)

Now when a player’s character spawns, our script will go through all of the parts in that character and will add them to our collision group. If that character runs into another character, the physics engine will see all of their parts being in the same group (that is configured not to collide with itself), and will let the characters pass through each other.

As our script is right now, any parts contained by the player’s character upon spawning will be handled by setCollisionGroupRecursive(). However, if any additional parts are added to the character after onCharacterAdded() has fired, such as a Tool or custom package, they will not belong to the same collision group as the character. In order to handle this, we can listen to the DescendantsAdded events on a character.

When a new descendant is added to a player’s character our script should check correctly if that object is a BasePart and add it to our player collision group accordingly. The best way to do this is to separate setCollisionGroupRecursive into another function setCollisionGroup, which handles only one object non-recursively.

local function setCollisionGroup(object)
  if object:IsA("BasePart") then
    PhysicsService:SetPartCollisionGroup(object, playerCollisionGroupName)
  end
end

local function setCollisionGroupRecursive(object)
  setCollisionGroup(object)

  for _, child in ipairs(object:GetChildren()) do
    setCollisionGroupRecursive(child)
  end
end

local function onCharacterAdded(character)
  setCollisionGroupRecursive(character)

  character.DescendantAdded:Connect(setCollisionGroup)
end

In addition to handling new objects added into a player’s character our script needs to handle when objects are removed. The best practice here is to keep track of the previous collision group that the object being removed was in. This requires us to store that information in a table when the object is initially being added into the player collision group. With this information, our script can listen to the DescendantsRemoving event to reset any objects removed to their old collision group.

local previousCollisionGroups = {}

local function setCollisionGroup(object)
  if object:IsA("BasePart") then
    previousCollisionGroups[object] = object.CollisionGroupId
    PhysicsService:SetPartCollisionGroup(object, playerCollisionGroupName)
  end
end

local function setCollisionGroupRecursive(object)
  setCollisionGroup(object)

  for _, child in ipairs(object:GetChildren()) do
    setCollisionGroupRecursive(child)
  end
end

local function resetCollisionGroup(object)
  local previousCollisionGroupId = previousCollisionGroups[object]
  if not previousCollisionGroupId then return end 

  local previousCollisionGroupName = PhysicsService:GetCollisionGroupName(previousCollisionGroupId)
  if not previousCollisionGroupName then return end

  PhysicsService:SetPartCollisionGroup(object, previousCollisionGroupName)
  previousCollisionGroups[object] = nil
end

local function onCharacterAdded(character)
  setCollisionGroupRecursive(character)

  character.DescendantAdded:Connect(setCollisionGroup)
  character.DescendantRemoving:Connect(resetCollisionGroup)
end

Now, when a character spawns all parts within that character are added to a table with the Id of their previous collision groups. If removed, those parts will be reassigned to their old collision groups. As well, any new parts will be assigned and unassigned in the same manner.

Altogether our script is the following:

local PhysicsService = game:GetService("PhysicsService")
local Players = game:GetService("Players")

local playerCollisionGroupName = "Players"
PhysicsService:CreateCollisionGroup(playerCollisionGroupName)
PhysicsService:CollisionGroupSetCollidable(playerCollisionGroupName, playerCollisionGroupName, false)

local previousCollisionGroups = {}

local function setCollisionGroup(object)
  if object:IsA("BasePart") then
    previousCollisionGroups[object] = object.CollisionGroupId
    PhysicsService:SetPartCollisionGroup(object, playerCollisionGroupName)
  end
end

local function setCollisionGroupRecursive(object)
  setCollisionGroup(object)

  for _, child in ipairs(object:GetChildren()) do
    setCollisionGroupRecursive(child)
  end
end

local function resetCollisionGroup(object)
  local previousCollisionGroupId = previousCollisionGroups[object]
  if not previousCollisionGroupId then return end 

  local previousCollisionGroupName = PhysicsService:GetCollisionGroupName(previousCollisionGroupId)
  if not previousCollisionGroupName then return end

  PhysicsService:SetPartCollisionGroup(object, previousCollisionGroupName)
  previousCollisionGroups[object] = nil
end

local function onCharacterAdded(character)
  setCollisionGroupRecursive(character)

  character.DescendantAdded:Connect(setCollisionGroup)
  character.DescendantRemoving:Connect(resetCollisionGroup)
end

local function onPlayerAdded(player)
  player.CharacterAdded:Connect(onCharacterAdded)
end

Players.PlayerAdded:Connect(onPlayerAdded)
Tags:
  • coding
  • gameplay
  • humanoid
  • collision
  • physics