Firework
As I’ve worked on games throughout the years, I’ve developed a framework to simplify what I actually need to write to wire up a game. It’s lightweight and battle-tested over a variety of projects, prioritizing easy script communication and networking.
Get it here - Firework, a general purpose framework - Creator Marketplace (roblox.com)
Features
- Automatic remotes
- Automatic submodules
- Runtime module registration
- Finding a module by name
- Loading order control
- Start, which runs after all scripts have been initialized
- Customizable initialize and start function names
Usage and Setup
To get Firework up and running,
- Place it somewhere both the server and client can find, like ReplicatedStorage
- Create a server script that calls the initialize function, for example:
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Framework = require(ReplicatedStorage.Framework)
Framework.Initialize(
script.CollisionGroups,
script.ChunkSystem,
script.MineSystem,
script.PlayerTracker
)
Like shown, send initialize a reference to each module you want to register. Do not use curly braces for a table. On the client side, it might be worth using WaitForChild.
The order that you send here will be the order that each module is loaded in, where their initialize and start functions will be called in the same order.
Note: Creating a client side is optional, but you would just run the same code in a client script.
This is an example of a setup I like to use. I find it helpful to name the scripts “Client” and “Server” respectively to help with identifying where errors occur.
Script Communication
By default, modules are registered by name. So, when “PlayerTracker” is registered in the above example, I can find it in any other registered module by using the Get feature:
function MineSystem.Initialize()
MineSystem.PlayerTracker = MineSystem.Framework.Get("PlayerTracker")
Of course, you don’t have to store Get results the same way I did here. However, initialize is the intended place to do it. Make sure your Get calls run after they have been registered!
You can also register things during runtime using RegisterRuntime. This will immediately call initialize, then start.
function MineSystem.Initialize()
MineSystem.Framework.RegisterRuntime(script.Mine, "MineClass")
For demonstration, I added the name “MineClass” to Mine. This is an optional argument for RegisterRuntime that allows you to provide custom names. Now somewhere else in code, I can retrieve this module with the same name:
... Framework.Get("MineClass")
Remotes
Remotes are set up using predefined table names. By default, these names are “Server” and “Client.”
It helps me to think to use the opposite context that the script is running in. If a script was running on the client, I would add a Server table for server-accessible functions.
In this example, MineSystem is on the server side. So, if we want to give the client access to a server function, we could do it like so:
local MineSystem = {}
MineSystem.Client = {}
function MineSystem.Client.Mine(player, position)
...
And for the client to run this code, we call to the framework:
...Framework.Fire("MineSystem", "Mine", position)
First provide the module name, then the function name, then the arguments. If you are calling this from the server side, the first argument provided will act as the target player.
The framework has a few options for using remotes. For the client you only have Fire and Invoke.
But on the server, you gain FireAllClients and FireAllExcept.
In the case of FireAllExcept, the first argument is the player not to fire.
function Framework.FireAllExcept(player, moduleName, remoteName, ...)
Notes
- You can find some preferences in the Settings module
- A “Framework” reference is added to any registered module
- All modules are registered before any initialize calls are made
- The server must initialize the framework if the client wants to use it
- Any functions made available to the client will pass the player argument first
- I may write documentation in the future, but the function names and arguments are obvious
Why release it when there are other frameworks?
I want to show off my work! Also, a lot of frameworks are clogged with useless features that just make it impossible to make sense of. It does exactly, only what I want, and I understand it at a glance. I want to know how every aspect of my game’s codebase works. I would recommend creating one of your own if you also feel this way.
Releases I’ve used this framework in:
Super Speed Run - Roblox
Power Fighting Tycoon - Roblox
Anime Fighters Tycoon - Roblox
send memes to your enemies to destroy them tycoon - Roblox
Defend your Christmas Tree! - Roblox
Game Recommender - Roblox
Hexagon Smash - Roblox
Updates
Submodules
Submodules Update
Added support for submodules! You can now register modules as part of other modules.To add submodules, include a Submodules table:
(the keyword for this can be changed in settings)
local Player = {}
Player.Submodules = {
script.DataStoreHandler,
script.PurchaseHandler,
script.Character
}
It uses the same register system as regular modules. The load order is done module first, then submodules. Finally, the submodules table is replaced with a dictionary.
If you want a more advanced way to find these submodules, you can provide a callback instead of a table.
local EffectSystem = {}
EffectSystem.Submodules = function()
local submodules = {}
for _, module in pairs(script:GetChildren()) do
table.insert(submodules, module)
end
return submodules
end
To get submodules, the Framework.Get method has been upgraded! You can now provide a chain of submodules after the initial module name.
Player.Framework.Get("Player", "Character")
If you want to perform a deep search, you can keep adding arguments.
(I haven’t extensively tested this, let me know if this has issues)
CombatSystem.Framework.Get("CombatSystem", "Finders", "GetPlayers")
Register and RegisterRuntime have also been upgraded! You can now pass a table to register the given module into. This is optional only for RegisterRuntime, where no table will default to Framework.Modules as usual.
Framework.RegisterRuntime(module, name, modules)
For example:
Character.Framework.RegisterRuntime(script:WaitForChild("Local"), nil, Character.Types)
Some other quality of life changes were made, including more descriptive errors. Let me know if you run into any problems!
Remotes yielding during loading
Remote Queue Update
I've made a small change that queues up server-to-client remotes while the framework is loading. I ran into an issue where the server was telling the client to do things that weren't loaded yet, so I added this setting as a remedy.QueueRemotesDuringLoading = true
In addition to this, you can now check if the framework is loaded or wait for it to finish loading.
-- to wait for loading:
Framework.WaitForLoaded()
-- to check if loading has completed:
if Framework.IsLoaded then
-- some code here
end
Note that IsLoaded is not a function, but a boolean property. That’s all for now!
UnreliableRemoteEvent Support
UnreliableRemoteEvent Support
With the introduction of the UnreliableRemoteEvent, I have added new syntax for marking remotes as unreliable. They are handled separately from normal remotes as to prevent functions marked as unreliable from being called reliably, and vice-versa.
To mark a function as unreliable, the new default keyword for this is “Unreliable” and is modifiable in the Settings module. Here’s an example:
local Test = {}
Test.Unreliable = {} -- notice that we need to create this table manually
function Test.Initialize()
-- dummy function
end
function Test.Unreliable.Print(player, text) -- can only be called unreliably
print(text)
end
return Test
If we assume the above is server code, we can call it from the client using the new FireUnreliable
Test.Framework.FireUnreliable("Test", "Print", "hello")
This syntax works on both the client and the server. However, the server must still provide a player argument for firing remotes. The server also has access to a few new unreliable methods:
Framework.FireAllClientsUnreliable(moduleName, remoteName, ...)
Framework.FireAllExceptUnreliable(player, moduleName, remoteName, ...)
These are variations of FireAllClients and FireAllExcept and function the same way, just using unreliable remotes. I am excited to see the applications of this new feature!
FireAllClientsNearby
FireAllClientsNearby
Today I added a new function out of necessity. You can now call Framework.FireAllClientsNearby
from the server, and you can also provide a callback to check distance if you need. This was added to try and reduce network usage for players far from an event that don’t necessarily need to be replicated to.
function Framework.FireAllClientsNearby(position, radius, callback, moduleName, remoteName, ...)
callback = callback or function(player, maxDistance)
if not player.Character or not player.Character.PrimaryPart then
return
end
local distance = (player.Character.PrimaryPart.Position - position).Magnitude
return distance < maxDistance
end
for _, player in pairs(Players:GetPlayers()) do
if callback(player, radius) then
Framework.Fire(moduleName, remoteName, player, ...)
end
end
end
In code, the default callback just tests character primary part distance from the given position. If you want to use the default, just provide nil to the function. The rest of the arguments are passed to the usual Framework.Fire
method.
I will update this framework regularly as I continue to develop it for new projects. Feel free to suggest ideas or changes!