PhysicsService

Create collision groups and customize how they can collide with each other!

by InsertYourself

Author Avatar

PhysicsService is very powerful and easy to use!

The purpose of this tutorial is to inform you about the limitless uses of PhysicsService and to instruct you on the intuitive, logical steps of how to use this service.

Before we start, here are some common methods and events that we will use throughout this tutorial. I will not be explaining any of these methods and events in detail, as this is a hard difficulty tutorial. You should know what these methods do, or at least can figure it out what they do based on their name.

Methods you should know:

Events you should know:

Now, rather than just showing you how to use the PhysicsService methods, let's create a scenario with a problem and try to work our way through it!

Our Scenario

It's your birthday and you're hosting a party in your house. You only want invited players to enter the party.

Task

Create an entrance door and only allow invited players to go through the door. How do we accomplish this? With PhysicsService!

img|80x80

Let's get Started!

First, let's get our Players service and PhysicsService. This will allow us to access the Players methods and PhysicsService methods!

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

Who's Invited?

Let's create a table of invited players to refernece from.

local invitedPlayers = {"Player1", "Player2", "Player3", "Player4"} -- These are the names of the players when we go in testing mode

Creating our Collision Groups!

Collision groups are named groups that share a collidable property relative to other collision groups. Just like instancing parts, collision groups' collision property is set to true by default (meaning they can collide with each other).

To create a collision group, we use the CreateCollisionGroup() method from PhysicsService! This method takes one argument, a string, which will be the name of our new collision group.

PhysicsService:CreateCollisionGroup("Entrance") -- A collision group for our front entrance door
PhysicsService:CreateCollisionGroup("InvitedPlayers") -- A collision group for players invited to the party

Setting our Collidable Property!

As stated before, all collision groups have their collidable property set to true by default, meaning all collision groups can collide with each other. We want invited players to go through the entrance door, so we have to set their relative collision group property to false

To do this, we use the CollisionGroupSetCollidable() method. This method takes 3 arguments. The 2 strings of our collision group names, and the 3rd argument is a boolean stating whether the two groups can collide with each other.

PhysicsService:CollisionGroupSetCollidable("Entrance", "InvitedPlayers", false) -- Our two collision groups can now go through each other

Adding parts to Collision Groups!

We've created our collision groups, but there's nothing inside them! Let's add some parts to our collision groups.

To add parts into collision groups, we use the SetPartCollisionGroup() method. This method takes two arguments. The part instance, and a string of the collision group name.

PhysicsService:SetPartCollisionGroup(workspace.Entrance, "Entrance") -- workspace.Entrance is our door and we are putting it into the collision group "Entrance"

BUT WAIT!

We can't just add the player by name. Collision Groups only accept parts to be in a collision group! Although our player is not a part, our character is made out of many parts! Parts such as RightLowerLeg, LowerTorso, etc...

This is where we apply the CharacterAdded event and get the descendants of the character because not only are your limbs (direct children of character) are parts, but also your accessories have Handles associated with them!

Although accessory handles are, by default, CanCollide = false, a roblox update may change this property. To make our game reasonably robust as possible, we use GetDescendants() rather than GetChildren(). And wrap everything in a PlayerAdded event

local function onCharacterAdded(character)
    local isInvited = false -- Initialize if the player is invited to the party

    for i = 1, #invitedPlayers do -- Cycle loop for #invitedPlayers times
        if character.Name == invitedPlayers[i] then -- If character name == the string at our current index [i]
            isInvited = true -- If character name is in our table of invitedPlayers then set isInvited to true
        end
        if isInvited then break end -- Once isInvited is true, break from the loop
    end

    if isInvited then -- If isInvited is true then proceed into the block of code
        for i,object in pairs(character:GetDescendants()) do -- Iterate through all descendants of character
            if object:IsA("BasePart") then -- Collision Groups only accept BaseParts
                PhysicsService:SetPartCollisionGroup(object, "InvitedPlayers") -- Add object to the Collision Group "InvitedPlayers"
            end
        end
    end
end

local function onPlayerAdded(player)
    if player.Character then -- If Player Loaded before the script ran
        onCharacterAdded(player.Character)
    end

    player.CharacterAdded:Connect(onCharacterAdded) -- Every time the player loads character
end

Players.PlayerAdded:Connect(onPlayerAdded)

But Wait! There's more :)

So far this code only works for blocky characters (no packages). This is because every player spawns into the game as a blocky character. And if a player has a package, roblox will replace those blocky body parts with your package body parts.

To clarify, the CharacterAdded event is being applied when your character spawns into the game as a blocky character (not with your package).

To optimize our code for players with packages (which is most players) we have to connect our onCharacterAdded() function to a DescendantAdded event. This will capture all descendants added to the character, such as the package body parts and accessory handles, and any other additions to the character model.

local function onDescendantAdded(object)
    local isInvited = false -- Initialize isInvited to false

    for i = 1, #invitedPlayers do -- Cycle through loop #invitedPlayers times
        if object:FindFirstAncestor(invitedPlayers[i]) then -- If the object has an ancestor with the same name of invitedPlayers at index [i]
            isInvited = true -- Then we know the object is part of an invited player
        end
        if isInvited then break end -- Break out of loop once we know the object is part of an invited player
    end

    if object:IsA("BasePart") and isInvited then -- Check if the object IsA "BasePart" and if the object is part of an invited player
        PhysicsService:SetPartCollisionGroup(object, "InvitedPlayers") -- Add object to the Collision Group "InvitedPlayers"
    end
end

local function onCharacterAdded(character)
    local isInvited = false -- Initialize if the player is invited to the party

    for i = 1, #invitedPlayers do -- Cycle loop for #invitedPlayers times
        if character.Name == invitedPlayers[i] then -- If character name == the string at our current index [i]
            isInvited = true -- If character name is in our table of invitedPlayers then set isInvited to true
        end
        if isInvited then break end -- Once isInvited is true, break from the loop
    end

    if isInvited then -- If isInvited is true then proceed into the block of code
        for i,object in pairs(character:GetDescendants()) do -- Iterate through all descendants of character
            if object:IsA("BasePart") then -- Collision Groups only accept BaseParts
                PhysicsService:SetPartCollisionGroup(object, "InvitedPlayers") -- Add object to the Collision Group "InvitedPlayers"
            end
        end
    end

    character.DescendantAdded:Connect(onDescendantAdded) -- Fired whenever a descendant is added to character
end

local function onPlayerAdded(player)
    if player.Character then -- If Player Loaded before the script ran
        onCharacterAdded(player.Character)
    end

    player.CharacterAdded:Connect(onCharacterAdded) -- Every time the player loads character
end

Players.PlayerAdded:Connect(onPlayerAdded)

OUR FINISHED CODE!

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

local invitedPlayers = {"Player1", "Player2", "Player3", "Player4"} -- These are the names of the players when we go in testing mode

PhysicsService:CreateCollisionGroup("Entrance") -- A collision group for our front entrance door
PhysicsService:CreateCollisionGroup("Invited Players") -- A collision group for players invited to the party

PhysicsService:CollisionGroupSetCollidable("Entrance", "InvitedPlayers", false) -- Our two collision groups can now go through each other

PhysicsService:SetPartCollisionGroup(workspace.Entrance, "Entrance") -- workspace.Entrance is our door and we are putting it into the collision group "Entrance"

local function onDescendantAdded(object)
    local isInvited = false -- Initialize isInvited to false

    for i = 1, #invitedPlayers do -- Cycle through loop #invitedPlayers times
        if object:FindFirstAncestor(invitedPlayers[i]) then -- If the object has an ancestor with the same name of invitedPlayers at index [i]
            isInvited = true -- Then we know the object is part of an invited player
        end
        if isInvited then break end -- Break out of loop once we know the object is part of an invited player
    end

    if object:IsA("BasePart") and isInvited then -- Check if the object IsA "BasePart" and if the object is part of an invited player
        PhysicsService:SetPartCollisionGroup(object, "InvitedPlayers") -- Add object to the Collision Group "InvitedPlayers"
    end
end

local function onCharacterAdded(character)
    local isInvited = false -- Initialize if the player is invited to the party

    for i = 1, #invitedPlayers do -- Cycle loop for #invitedPlayers times
        if character.Name == invitedPlayers[i] then -- If character name == the string at our current index [i]
            isInvited = true -- If character name is in our table of invitedPlayers then set isInvited to true
        end
        if isInvited then break end -- Once isInvited is true, break from the loop
    end

    if isInvited then -- If isInvited is true then proceed into the block of code
        for i,object in pairs(character:GetDescendants()) do -- Iterate through all descendants of character
            if object:IsA("BasePart") then -- Collision Groups only accept BaseParts
                PhysicsService:SetPartCollisionGroup(object, "InvitedPlayers") -- Add object to the Collision Group "InvitedPlayers"
            end
        end
    end

    character.DescendantAdded:Connect(onDescendantAdded) -- Fired whenever a descendant is added to character
end

local function onPlayerAdded(player)
    if player.Character then -- If Player Loaded before the script ran
        onCharacterAdded(player.Character)
    end

    player.CharacterAdded:Connect(onCharacterAdded) -- Every time the player loads character
end

Players.PlayerAdded:Connect(onPlayerAdded)

img|80x80

img|80x80

img|80x80

Final Thoughts

Dealing with characters is always complicated. But dealing with single objects it's a lot more simple! I hope you learned a lot about the fundamentals of PhysicsService!

Also, to set a Collision Group nonCollidable with itself, it's like this

PhysicsService:CollisionGroupSetCollidable("PlayerGroup", "PlayerGroup", false)

If we added every player's part to the PlayerGroup Collision Group then every player will be able to go through each other!

This tutorial was created and edited by InsertYourself Have a wonderful life!

View in-game to comment, award, and more!