PubSub for Roblox (Publish/Subscribe Eventing)

My PubSub for Roblox module enables 4 way code communication via an Event Management system.

(Server/Server, Server/Client, Client/Client, Client/Server)

PubSub works with FilteringEnabled and allows for easy Publish and Subscribe functionality.

No Copylock Sample on Roblox: https://www.roblox.com/games/896539588/Event-Management-Demo

Why is this cool? Well, let’s say I wanted to announce a player has joined the game in chat.

The Player joining is something we connect to on the server [Players.PlayerAdded]

The chat is on the client, so the server and client need a RemoteEvent to communicate through.

You COULD create the RemoteEvent as a static object in ReplicatedStorage, then write code on both the server and the client to access that event and handle it as needed.

Of course, you have to hope the event is there when both the client and the server are looking for it.

You also have to do this for every RemoteEvent in your game…

ugh

Better way? PubSub.

PubSub creates RemoteEvents when they are needed and cleans them up when they aren’t.

PubSub ensures RemoteEvents are always available prior to attempted use.

PubSub also supports client to client and server to server eventing. RemoteEvents (alone) do not.

Check it out on GitHub: https://github.com/HuotChu/roblox-pubsub

Example:
ServerScriptService > Script

local Players = game:GetService('Players')
local ReplicatedStorage = game:GetService('ReplicatedStorage')
local PubSub = require(ReplicatedStorage:WaitForChild('PubSub'))

local OnPlayerConnected = function (player)
    wait(1)
    PubSub.publish('AnnouncePlayer', player.Name)
end

Players.PlayerAdded:connect(OnPlayerConnected)

StarterGui > LocalScript

local StarterGui = game:GetService('StarterGui')
local PubSub = require(ReplicatedStorage:WaitForChild('PubSub'))

PubSub.subscribe('AnnouncePlayer', function (playerName)
	StarterGui:SetCore('ChatMakeSystemMessage', {
		Text = playerName..' joined the game!'
	})
end, 'ChatMain')

 

  • Note: I am currently working on v 2.0 of this which includes an authorization module to protect Remote Events/Functions
13 Likes

Wow you keep on making useful modules, thanks! :smiley:

1 Like

Thanks, and you’re welcome! :blush:

Could you give examples on things like Server>Server, Client>Server, and Client>Client? I already have a pretty nice system for server<->client communication, but I never considered expanding it to server<->server and client<->client. I’m pretty interested in what you’ve got and how it works.

1 Like

Here’s an example of using PubSub in the Workspace to listen to events from the server: (server <-> server)

Workspace > Model > Script

  • Pretend the example Model is a mock freeze ray that will freeze a player.
  • The freeze ray has a FreezePlayer method that, when called with an array of one or more players, will target them and fire the freeze ray at them
  • The server has a script that decides which players should be targeted and when. The freeze ray needs to know who and when to fire without polling for that information.
local ReplicatedStorage = game:GetService('ReplicatedStorage')
local PubSub = require(ReplicatedStorage:WaitForChild('PubSub'))

PubSub.subscribe('FireFreezeRay', FreezePlayer, 'FreezeRayScript', 'noRemote')

FireFreezeRay is the name of the Topic to listen for (basically, the event name).

FreezePlayer is defined somewhere in the example script which is attached to the Freeze ray model. Arguments passed by the server to PubSub.publish will be passed along to the FreezePlayer function.

FreezeRayScript is the unique name for this subscriber. Call it whatever you want.

noRemote is set, because we don’t need a remote event created for this. The client does not care about the FreezePlayer event.

ServerScriptService > Script

  • For this example, pretend the server picks a random number of players and then freezes them.
local ReplicatedStorage = game:GetService('ReplicatedStorage')
local PubSub = require(ReplicatedStorage:WaitForChild('PubSub'))

local mockFunction = function ()
    --- code to calculate random players to target
    return {player1, player3, player7}
end

PubSub.publish('FireFreezeRay', mockFunction ())

You might also want non-workspace scripts on the server to listen to each other.

For example: You might have a “state machine” script that publishes when the game state has changed. For instance, a game reaches the minimum required players, so it changes state from 1 (not enough players) to 2 (enough players). You might have one or more other scripts on the server that do things when the state changes, such as a ‘PlayerControl’ script that moves all the players into their initial positions when the game starts. The state machine can Publish ‘StateChanged’ along with the new state, and the PlayerControl script can subscribe to ‘StateChanged’ and check that if the state is == 1, move all the players into starting positions.

Client to Client

Let’s say you make a full screen UI component called overlay and you make it black and set Transparency to something low like 0.1 or 0.2. You want this UI layer to display behind any other dialog that is opened (like store, inventory, etc). The overlay can have a script that subscribes to a topic ‘ShowOverlay’ and it can hide or show based on the argument passed. Then, each time you add a new UI screen, it can simply publish ‘ShowOverlay’ along with true|false and you get a reusable component that is easy to ‘hook up’.

1 Like

I posted this on reddit showing another common use of PubSub for Client to Client eventing, so I thought I would add it to the examples here. The op wanted to type into a textbox and see the input in a separate textlabel.

LocalScript inserted under TextBox

local ReplicatedStorage = game:GetService('ReplicatedStorage')
local PubSub = require(ReplicatedStorage:WaitForChild('PubSub'))
local textbox = script.Parent

onChange = function (prop)
    if prop == 'Text' then
    	PubSub.publish('ShowPlayerText', textbox.Text)
    end
end
    
textbox.Changed:Connect(onChange)

What’s happening here is that the Change event fires when any property changes. It passes along the name of the property that changed, so we use that to filter out all the changes we don’t care about. If the “Text” property changes, we publish a made-up event name and pass along the information to any subscribers. Let’s create a subscriber now which will make a second GUI to display what is typed in the TextBox.

LocalScript inserted into StarterGui > ScreenGui > TextLabel

local ReplicatedStorage = game:GetService('ReplicatedStorage')
local PubSub = require(ReplicatedStorage:WaitForChild('PubSub'))
local textlabel = script.Parent
    
local onShowPlayerText = function (txt)
	textlabel.Text = txt
end

PubSub.subscribe('ShowPlayerText', onShowPlayerText, 'myUniqueHandleName', 'noRemote')

So the only thing complicated here is knowing what to tell PubSub, but that part is actually easy as well. The first argument is the event name we are publishing the data to; we set that name in the first script. The second argument is your call-back function to handle the event. The third argument is a unique name for this subscriber. It is used to optionally unsubscribe the event handler later and it also keeps things organized internally. The fourth argument is the string ‘noRemote’, which simply tells PubSub that the client is talking to the client, so don’t bother making a new RemoteEvent as it is not needed for this communication.