Hey developers!
RbxEnv is a lightweight framework designed for variable sharing and codebase organization. As it’s known using that _G.
and SHARED
is a bad practice, I’ve often found myself wanting to create variables that can be shared between scripts in the game. I didn’t like declaring the same variables in almost every script for assets and wanted a framework that allows you to declare global variables and handle them in a much organized manner.
Though this can be achieved through module scripts, I decided to create a framework dedicated to this purpose as a passion project. This project was something I contributed to in my free time and I’m open sourcing it now that I’m pretty much finished with it’s core functionality if anybody wants to add on to it or suggest potential use cases.
Had a bit of trouble trying to deploy my docs via github pages so I’ll put the step by step tutorial on here . API for all the functions are already listed in the modules
Get the model here: https://create.roblox.com/store/asset/94470611473555/RbxEnv
Github Repo: GitHub - threadous/RbxEnv
Getting Started
Import the RbxEnv package from the Roblox Marketplace and place the folder in ReplicatedStorage
.
For the sake of this tutorial, the getting started will be guiding you to create a Centralized framework. In the future, depending whether you intend to create a Centralized or Decentralized framework, your set up might vary.
What’s the difference?
-
Centralized: Most of the environment structure is avaiable to you in a fully organized module script. Heirarchy can easily be determined saves you the time of writing code to create environments. It is a much more refined way to set up your framework. HIGHLY RECOMMENDED for environments that are going to be replicated to the client.
-
Decentralized: You will be able to build environments in scripts using the
RbxEnv.new
function to set up all your environments (you can still achieve this through the centralized version). Using this setup method may result in some environments and variables not replicating the client. This setup was originally developed in the raw build of the framework for testing and small scale purposes.
Setting up
As for this tutorial, it will be guiding you through setting up a centralized framework. You may name the scripts/folders as you may in this tutorial, names are indicated just for you to follow along. If names are not supposed to be changed or set to a certain name, it will be explicitly stated.
Head over to ServerScriptService
and create a folder (Core) and add a script (Main) in that folder followed by a module script in the same folder (GameConfig Name it exactly as GameConfig)
Next, open the GameConfig
module script and add the following code:
return {
["GameEnvironments"] = {
},
["GameNetworks"] = {
},
["GameConstants"] = {
},
}
Create An Environment
In the same GameConfig module script, add the name of your environment in the GameEnvironments table as so:
["GameEnvironments"] = {
"EnvironmentBasics",
}
Add the name of your environment as the key
to an empty table {}
in the GameNetworks
and the GameConstants
part as well.
This creates a new environment called EnvironmentBasics for you to work in and code. This will also be replicated to the client, meaning any values/variables/events you store within this environment will be available to the client to access. Any environment not declared in GameConfig will NOT replicate to the client as long as it is created AFTER the RbxEnv initialization.
Environment Mechanics
The Basics
Let’s start by creating a variable that would be shared by both the server and the client through the GameConfig
module.
To add a constant Gravity
to the environment EnvironmentBasics
we can do:
["GameConstants"] = {
["EnvironmentBasics"] = {
Gravity = 9.8,
}
}
Doing this creates a variable in the EnvironmentBasics
that will be carried over to the client. Changing this variable via a script will not replicate across the game. Once initialized, this value will stay be initialized to both the client and the server. In order to change this, you must add a method so that when the server changes this value, the client also does so.
Variable Mechanics
The previous code was aimed at creating variables shared across both the client and the server. Variables would only replicate if declared in the GameConfig
file.
But in cases where you wouldn’t want variables replicated and want to create variables AFTER initializing the environment, you could create a variable directly:
Let’s create a new environment called coreEnvironment
and initialize a few variables in it.
IMPORTANT
Make sure that your code yields for RbxEnv to first initialize then create any non-replicating environments
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Package = ReplicatedStorage:WaitForChild('RbxEnv')
local RbxEnv = require(Package.Env)
-- // Creating a new environment
-- // Perhaps add a wait for 2 seconds before declaring the variable so the environment is not initialized along with RbxEnv
task.wait(2)
local coreEnvironment = RbxEnv.new("coreEnvironment")
Now that we have our code set-up, we can create a variable. For the example, it will be called GameData
The following code block initialized and declares a variable called GameData
in the coreEnvironment
and sets the value of the variable to "Very Important Data"
.
--// Creating a variable
coreEnvironment:Variable("GameData").set("Very Important Global Data")
Getting a variable value
To reference the same variable value in the same script, you would do:
coreEnvironment:fetch("GameData") -- // Returns the value "Very Important Global Data"
Fetching a variable
To re-assign a value or change the value of a variable, you would use the Get
method:
coreEnvironment:Get("GameData").set("Not so important anymore")
The Get
method returns the variable
structure which has the set
method that allows you to set its value.
Loading an Environment
Accessing an environment is relatively simple. You must always wait before getting the environment or check to make sure if the environment you are trying to access has already been defined.
In some cases, you might be declaring all your environments at the start of the script but add a task.wait(1)
to make sure that any other scripts that are creating the environment have ran first.
Now that we have coreEnvironment
initialized, create a new script so we can access this environment:
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Package = ReplicatedStorage:WaitForChild('RbxEnv')
local RbxEnv = require(Package.Env)
task.wait(1)
local coreEnvironment = RbxEnv.GetEnvironment("coreEnvironment") -- Get the coreEnvironment (environment class)
After defining coreEnvironment
you can modify/change this environment as you would and fetch any of its variables.
Environments go great with Networks
; the next page will talk about how RemoteEvents
and BindableEvents
are embedded into Environments
through a Network
.
Network Mechanics
What is an env Network?
A Network
class represents a single remote event. It is a table that contains other information including a direct path to your remote event.
All the examples provided on this page are either through a server-script
or the GameConfig
file.
Why use Network?
Env Networks were built to go hand in hand with environments. Developers often find it repetative to define the same ReplicatedStorage/Framework/Remotes/Events
in almost every script. Networks work well with Environments
. Any Network
created is stored as a variable with the particular environment assigned to it so they can be accessed by any script by just defining the Environment
.
Networks also simplify organization and save you time from having to handle remote events manually. It also offers additional features and control over remote events such.
Basic Network Initialization
In the most basic form, a network can be created by:
coreNetwork:CreateNetwork("BuyCurrency")
The code creates a Network
class with a RemoteEvent
with the name `“BuyCurrency”
However, this was designed as an early functionality of RbxEnv is not recommended to be created this way.
Creating a Network
The best way to avoid any logical errors when creating a Network is to directly to type it in the GameConfig
file.
As seen earlier, the GameConfig
module had 3 tables. Of the 3, one of them named GameNetworks
stores all the networks with its corresponding environment.
Why not create networks manually?
- A
Network
is essentially a remote event and both the client and the server must have access to it in order for it to work. Though a remote event created on the server will replicate to the client, aNetwork
will not. ANetwork
is a class and is stored in an environment. So any changes to an environment after initialization will not carry over to the client. Creating aNetwork
manually does not guarantee a successful replication, however following a centralized system and using theGameConfig
module does.
For this example, the code will be creating a network called BuyCurrency
for the environment EnvironmentBasics
:
["GameNetworks"] = {
["EnvironmentBasics"] = { -- Environment
"BuyCurrency", -- Network name
},
},
Typing the name of the network auto-creates a network for you under the environment assigned and can now be accessed by any part of your game (server/client) by accessing the environment.
Accessing a Network
Once a network is created, it is stored as a variable in the Environment
it was assigned (in this case- BuyCurrencyNetwork
was stored in EnvironmentBasics
). The variable name is the same as the name of the network.
To get a network:
local buyCurrencyNetwork = EnvironmentBasics:GetNetwork("BuyCurrency")
Connecting a Network (Server & Client)
Networks-just as remote events-are useless if they aren’t connected to a function. You must first write the bootstrapping code for each network before firing them.
The bootstrapping code for a network is very similar to a RemoteEvent.OnServerEvent:Connect(function())
, however a lot of the other text is skipped when using Networks
.
There isn’t a big difference of how Networks
are grabbed on the server or the client. The same function can be used to write bootstrapping code to connect the events. However, keep in mind Networks
follow the same convention remote events do. (player parameter comes first in a .OnServerEvent)
In this example, the following code writes the functionality for when the BuyCurrency
network is fired:
buyCurrencyNetwork:Grab(function(player, text) -- you may include any other parameters after the player parameter if you are firing the network with arguments on the client.
-- Do something in the remote event
print("Text from the player: "..text)
end)
Firing a Network (Server & Client)
Firing a network is very similar to that of a remote event.
buyCurrencyNetwork:Fire("My text argument goes here!") -- add player parameter if firing from the server
Rather than :FireClient()
or :FireServer()
, you can just call :Fire()
. The Network will automatically call the correct remote event method based on the file from which you call it from (server or client).
As stated before, the Network
follows the same convention as a remote event. Meaning that the parameters for player or any other arguments will also follow the same way they do with a remote event.
Suspending a Network
Network
offers an additional functionality which allows you to suspend
your networks. Meaning that a network cannot be fired if it is suspended. This is a great functionality to safeguard your game from exploiters as a Network fired when suspended auto overwrites any of the bootstrapping code to avoid making the server code run.
The syntax for it is as follows:
buyCurrencyNetwork:Suspend()
buyCurrencyNetwork:Unsuspend()
Your network will remain suspended unless you unsuspend it.
Keep in mind that network suspension is a functionality called after RbxEnv initialization. When suspending, you must suspend the network on both the client and the server (only server is required but client is also recommended) but you must unsuspend the network on both the server and client if you had suspended the network on the client.
Important Info
-
A
Network
can only be accessed from the environment it was declared in. Code will not run if you try to access a network declared inEnvironmentBasics
and try to access it from another environment. -
Creating a
Network
after RbxEnv initialization is not recommended (it will not work). Use theGameConfig
module to setup all your environment instances. -
Do not use the
:setEnvironment
method for theNetwork
class. Doing so will change the the network parent to another environment on either the server or the client but not both and may result in an unorganized codebase.
Additional Info
Networks
are remote events. The next page contains information about Executors
which essentially act like bindable events.
Executor Mechanics
What are Executors?
Executors act like BindableEvents
(server-server communication). Unlike a Network
, an Executor
does not contain a literal BindableEvent
. Executors are not environment restricted variables (like Networks); they can be accessed through any environment (explained more later on this page - executor utilization)
Why use Executors?
Similar to a Network
, an executor saves developers the trouble of manually creating bindable events and defining them. Executors were built compatible to RbxEnv to provide a smoother workflow.
Creating An Executor
Unlike a Network
or a GameConstant
, an Executor
is either server only or client only as they are used for communication between server-to-server or client-to-client, which implies that executors need not be replicated so they need not be defined in the GameConfig
module.
The following code creates an executor of the name StatCounter:
RbxEnv:AddExecutors("StatCounter")
local statCounter = RbxEnv:GetExecutor("StatCounter")
Instead of adding only 1, you can chose to initialize multiple executors at once:
RbxEnv:AddExecutors("StatCounter", "GameCounter", "PlayerData")
To get an executor from another script:
local statCounter = RbxEnv:GetExecutor("StatCounter")
local gameCounter = RbxEnv:GetExecutor("GameCounter")
Keep in mind that though you won’t have to wait for an environment to be initialized to create/fetch executors, you must still wait to check if the executor itself has been initialized
Utilizing an Executor
Similar to a Network
, you must first write bootstrapping code for your Executor
before firing it.
You can use the CatchJob
method to connect the executor to your own function:
local statCounter = RbxEnv:GetExecutor("StatCounter")
statCounter:CatchJob(function(someVariablePassed)
print(someVariablePassed)
end)
someVariablePassed
is the variable being passed through the statCounter
executor in this case.
The following code shows how you would fire an executor (NOTE: Bootstrapping code must already have been written and compiled to run prior to firing an executor)
RbxEnv:TagJob("StatCounter", "The variable I Passed!")
The code above fires the Executor
called StatCounter
and passes the string "The variable I Passed"
as an argument.
You can add as many variables to pass through the Executor as you wish:
RbxEnv:TagJob("StatCounter", num1, num2, num3, num4)
And you can include this in your bootstrapping code
statCounter:CatchJob(function(num1, num2, num3, num4)
print(num1 + num2 + num3 + num4)
end)
Info
You can find the API for each function in its respective module. If you’re interested in looking at some code using RbxEnv, I’ve added majority of my test code in the Demo
folder in the repo.
Please do let me know of any bugs or problems (I’ll try my best to fix them). As stated earlier this framework was built as a passion project. I’d love to hear your feedback on this
Feel free to use this framework in your projects. Change things up, play around with it and add your own code to it! I’m open to suggestions.
If you’re having trouble with set up or utilization in general, shoot me a PM via the DevForum or reply to this post and I’ll be able to help you out.
Thanks for reading. Hope you find this resource helpful!