RbxEnv - A lightweight variable sharing framework

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 :smile: . 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, a Network will not. A Network 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 a Network manually does not guarantee a successful replication, however following a centralized system and using the GameConfig 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 in EnvironmentBasics and try to access it from another environment.

  • Creating a Network after RbxEnv initialization is not recommended (it will not work). Use the GameConfig module to setup all your environment instances.

  • Do not use the :setEnvironment method for the Network 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 :grinning:

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!

3 Likes