Introduction
The “Deity” framework is a modular game framework (Heavily modified BLU Framework) largely inspired by Knineteen19’s BLU and hexadecagons’s FreeBird framework, but with rewritten code, more organized to fit my needs. It creates a quick and simple workflow, it adds automatic module-requiring and built-in methods that run on start-up or are connected to their respective events automatically and only once for the client and server to reduce the total number of connections in the game. If you would like more info on BLU Framework the devfourm link will be listed in the credits.
Uncopylocked Testing Place
The Plugin
Updates
Show/Hide Content
06/08/24
- Plugin was released
Framework Set Up
Show/Hide Content
In order to set up the framework, you can simply press the “Set Up” button. Ideally, you do this before starting the development of a game, but if you’re looking to add your own modules to this framework, you can simply press the “Set Up” button and move your modules around until they’re all implemented properly.
In order to remove the framework, you can press the “Remove” button. This button keeps track of which objects were added as part of the Deity framework using tags in order to make sure it never deletes any of your own objects or code.
Lastly, pressing “Set Up”, automatically runs “Remove” before setting up, so if you simply want to update your framework to contain the most recent changes, you can just press “Set Up” and adjust your modules to fit the framework.
Framework Stages
Show/Hide Content
To summarize what ModuleHandler does, it essentially runs require() on every single module script inside of the “Client” and “Server” handlers and handles various different built-in module methods, which are indicated by two underscores (__) before the function names. ModuleHandler runs in a series of steps upon the .Start() function being called, which can be broken down like so:
Stage 1 - Requiring
All modules are require()'d, running their base code and storing their return values.
- Modules will only be required if they exist in a variety of specific locations:
- Shared locations:
- ReplicatedStorage.Modules.Deity.Comm (folder)
- Server locations:
- ServerScriptService.Server.Managers (folder)
- ServerScriptService.Server.Modules (folder)
- Client locations
- StarterPlayer.StarterPlayerScripts.Client.Managers (folder)
- StarterPlayer.StarterPlayerScripts.Client.Modules (folder)
- Shared locations:
Stage 2 - Initialization
For all modules who have one defined, the :__init() functions will be ran (note the use of a colon, which I’ll explain later). This is similar to Unity’s Start() method, which runs right when the game begins, but after the require() phase.
Stage 3 - Built-in Events
There are various other built-in events aside from the :__init() method, such as the events of RunService, UserInputService, and more. If no modules contain the corresponding methods for an event, that event is never connected to, so you won’t have to worry about unnecessary connections.
The current events that are set up already are as follows, but it’s possible to modify the ModuleHandler module to add more events:
- Shared (client & server):
- :__PlayerAdded(player)
- Not really necessary on the client, as the :__init() function is basically the :__PlayerAdded of client modules.
- It is necessary however if you wanted to check when other players are added from the client, but I’m not sure if you would ever need to.
- :__CharacterAdded(char, hum, root)
- No more :WaitForChild()ing for humanoid and root part! It does it for you!
- For server modules, this runs for all player’s characters, but for client modules, it only runs for the local player.
- All CharacterAdded connections are automatically disconnected when a player leaves, so you don’t have to worry about memory leaks.
- :__CharacterDied(char)
- Same thing with :__CharacterAdded(), it runs only for the local player’s character on client modules, but for all player’s characters on server modules.
- :__StateChanged(…)
- This is connected to the humanoid.StateChanged event, and works the same as with :__CharacterAdded() as well.
- :__PlayerRemoving(…)
- When a player leaves the game, fairly self-explanatory.
- :__PreSimulation(…)
- Connects to RunService.PreSimulation
- :__PostSimulation(…)
- Connects to RunService.PostSimulation
- :__PlayerAdded(player)
- Server only:
- None currently
- Client only:
- :__PreRender(…)
- Connects to RunService.PreRender
- :__InputBegan(…)
- UserInputService.InputBegan
- :__InputEnded(…)
- UserInputService.InputEnded
- :__InputChanged(…)
- UserInputService.InputChanged
- :__TouchPan(…)
- UserInputService.TouchPan
- :__TouchStarted(…)
- UserInputService.TouchStarted
- :__TouchEnded(…)
- UserInputService.TouchEnded
- :__JumpRequest(…)
- UserInputService.JumpRequest
^ Also all the old RunService ones for compatibility
- UserInputService.JumpRequest
- :__PreRender(…)
Creating a Basic Module
Show/Hide Content
This Framework comes with many built-in module templates, some of which can be very useful for complex OOP-structured code, but for now we’ll start with the “Plain Module” template under the “Create Module” button.
As you can see, it pops up a window in which we can insert the module’s name, select the location we want to insert the module, and press create to create and open the ModuleScript.
If you’re familiar with OOP-structured code, you can also check out the template modules for classes and inherited classes, or you can structure your own modules however you’d like as well, so long as they all return tables, it will automatically add some comments to keep everything clean aswell such as who created the module, the date created, etc etc.
Note: In order to disable a module from requiring and initializing, you can add an attribute called “Enabled” and set it to false, similar to with a regular or local script, though it will probably break some stuff if other modules require it.
Client-Server Communication
Show/Hide Content
Signal
Requiring the Signal Module
The primary method for client-server communication in this framework is the Signal module, which is heavily inspired from @Hexadecagons work yet again, but essentially handles remote events/functions for you (along with options to replace bindable events/functions). It essentially makes so you’ll never need to create a Remote/Bindable Event/Function ever again for any of your code, and can simply use specific string keys to indicate which fires go to which listeners.
Below is an example of how to use this signal module.
_G.Require() versus require()
The difference with using this _G.Require() in place of just require() is relatively minimal outside of convenience, but overall it allows you to specify the name of the module as opposed to its path, which enables much greater ability to move modules around either for organizational purposes or even between client, server, and shared locations to change what has access to it. Do note that, since modules are required through using their instance names rather than their paths, every module will need a unique name or ModuleHandler will run into some issues.
As you can see in the code for _G.Require() itself (located in ModuleHandler), this function simply waits indefinitely for a module of the given name to appear in the ModuleHandler.RequiredModules table and returns the return value of that module once it does. If it takes longer than 8 seconds to find that module, it will run the _G.Warn() function, which is just like warn() but it includes a traceback similar to an error (using TestService to make it blue). This warning is similar to :WaitForChild()'s infinite yield warning in that it doesn’t stop the code from waiting, but is a warning that it may end up waiting indefinitely and something is probably wrong.
Thanks to the fact that _G.Require() waits for the module to be loaded allows modules to automatically sort the order that they load in based on what modules _G.Require() what other modules. For example, a module that _G.Require()'s the Signal module will always finish loading after the Signal module, as it will wait for Signal to finish loading before moving past the _G.Require().
This is useful for handling dependency issues, but it can also lead to issues where one module that’s required by several others doesn’t ever finish loading/being required, thus leading to a confusing mass of infinite yield warnings. I’ve found, through using this framework for my own projects, that the best way to find the source of a mass of infinite yield warnings is to start with one of the modules that’s giving the infinite yield and simply putting a breakpoint at the top, finding which line it waits indefinitely at, going to that module, and repeating the process until you find what module the issue is in.
Signal’s Main Functions
Back to the Signal module, let’s go over the main functions it has. The current main functions are as follows:
- Shared
- Signal.Fire(key, …)
- This works like a bindable event, firing from server to server or from client to client.
- Signal.Invoke(key, …)
- This works similarly to a BindableFunction’s :Invoke() method and goes from server to server or client to client.
- Signal.Listen(key, callback)
- This is like connecting to an event, but there’s one key distinction. This function will listen to remote fires, remote invokes, bindable fires, and bindable invokes. It listens to it all, so use this with caution, especially if you’re going to use this on the server, as it makes so clients can run the provided callback function as well.
- Signal.ListenRemote(key, callback)
- This works like connecting to a RemoteEvent or RemoteFunction, and only listens to fires and invokes from the opposite end (client/server).
- Signal.ListenBindable(key, callback)
- This works like connecting to a BindableEvent or a BindableFunction, and only listens to fires and invokes from the same end (client/client or server/server)
- Signal.Wait(key)
- Works like running :Wait() on a Roblox event, waiting for it to fire once and returning the return values for that first fire.
- Listens to both remote and bindable.
- Signal.WaitRemote(key)
- Rather self-explanatory at this point, but waits only for remote fires and invokes.
- Signal.WaitBindable(key)
- Again, self-explanatory, but waits for only bindable fires and invokes.
- Signal.Fire(key, …)
- Client
- Signal.FireServer(key, …)
- This lets you fire server scripts or modules that are listening with any arguments you want that come after the key argument.
- Signal.InvokeServer(key, …)
- Works like RemoteFunction:InvokeServer() in the sense that it waits for the function to complete and gets a return value.
- Signal.FireServer(key, …)
- Server
- Signal.FireClient(player, key, …)
- Works the same as RemoteEvent:FireClient(), but again, with the key argument.
- Signal.FireAllClients(key, …)
- Signal.FireAllClientsExcept(player, key, …)
- This is useful for replicating visual effects to all other clients
- Signal.InvokeClient(player, key, …)
- Works the same as RemoteFunction:InvokeClient()
- Signal.FireClient(player, key, …)
Again, credit for this module goes almost entirely to @Knineteen19, @Hexadecagons as this is simply my own modified version of one of their modules.
Transverse
Show/Hide Content
Introduction
This module could technically go under “Client-Server Communication” as well, but I figured it was complex enough to have its own category. It was what I originally made when I decided to “make a framework”, though I had never really even used a framework before, I just heard that the Knit framework allowed you to make your own custom “services”, so I essentially just stole that idea and made my own version of that (but it doesn’t include the client controller portion). This module isn’t by any means necessary in order to use this framework, but probably the nicest thing about it is both the convenient/familiar syntax and the way it can organize a server module to have specific methods and properties that are accessible from the client. What it’s not good at is back and forth communication between client and server, however, given that it doesn’t have the “controller” portion that Knit also includes, but back and forth communication can be achieved either using Signal from earlier or using the “events” you can give services.
The basic idea is to create a “service” on the server side, probably in a module given that the framework is built on modules. You can then add properties, methods, and events to this object, similar to what a real service would have, and you can specify for each property whether it’s accessible to the client, the server, or both, using a number argument (0 is client-only, 1 is server-only 2 is both, in most cases). The best use case I’ve found for this module is to create it in a server module’s __init() function, connecting the modules methods to specific methods that you create for the service that you want the client to have access to. For example, if you created an “InventoryService”, you might want to add a “GetInventoryData” function to the InventoryService server module, and then you’d want to create a method for the InventoryService service, which would connect to the module’s GetInventoryData function, allowing the client to run that function, as well as the server.
Additionally, I’ve also found it can be useful to add a metatable to the module table that creates this service after creating it (like setmetatable(moduleName, {__index = service})
), as the service object is actually just a table with a meta table attached, and adding the __index metamethod allows other server modules to access the events, properties, and methods of this service through the module itself, rather than having to get the service through Transverse like the client does.
The only issue with that convenience is that it theoretically removes the point of having methods be accessible from the server, since you can just access them in the module itself, but what it is still useful for is shared modules that run code on both the client and server, depending on the context. For example, if you made code in a ReplicatedStorage module that allows a character (not necessarily a player) to sprint by running a specific function, you might want to use that code both for players as well as for NPCs, so as to not have to rewrite all the same code just to make an NPC. The only issue with that, is that any Signal fires to what would be client to server now have a chance of being either client to server or server to server, in the case that the fire is coming from an NPC. This makes Signal fires and invokes a whole process involving if statements and RunService checks. But with Transverse services, the syntax for running service methods and getting service properties is the exact same for both server and client, so it removes the need to run different code on client and server, and can be useful for things like that sprinting module.
Functions
Long-winded introduction away, let’s get into the actual functions of the Transverse module.
- Shared
- Transverse.GetService(name: string, timeout: number?, needsReady: boolean?): service object
- You don’t really need to worry about the timeout and needsReady arguments, as the timeout is basically unnecessary at this point (it used to error when the timeout time passed, but now it just warns and keeps waiting for the service), and the needsReady was just something I need to add for a specific point where Transverse.GetService() is called within the Transverse module functions themselves.
- Provide the same name you used in Transverse.CreateService() and you’ll get a service table object with metatables set up in such a way that you can run the methods and get the properties just like with a normal service and it works basically the same.
- Transverse.GetService(name: string, timeout: number?, needsReady: boolean?): service object
- Server only
- Transverse.CreateService(name: string): service object
- Providing a name, you can create a service object for which you can add events, properties, and methods to. This function returns the initial service object.
- service:CreateMethod(methodName: string, serverClientIndex: number, methodCallback)
- This is a method you run on the service object itself to create a new method/function, providing the name of the function, the serverClientIndex (0:Client, 1:Server, 2:Both), and the function to connect the method to.
- Any other script, client or server (depending on the access level provided) can run the method by getting the service with Transverse.GetService(), getting the return value, and then running service:MethodName(arguments). Do note, however, that from client-server method calls, the player is provided automatically as the first argument, given that it works through remote functions and it’s nice to have the player in almost all cases anyway.
- service:CreateEvent(eventName: string, serverClientIndex: number)
- Same deal here, but this time it’s an event that you can fire from the server and connect to from either the client, server, or both, depending on the serverClientIndex.
- Given that events work by using RemoteEvents and BindableEvents, running :Connect() on one of these events returns the connection object itself, so disconnecting it would work the exact same as it would with a regular service as well, storing the connection in a variable and running connection:Disconnect() later on.
- In the case of events, they can always only be fired from the server, but the serverClientIndex specifies what can :Connect() to the events, as that information is necessary to know what kind of remote objects to create (remote events, bindable events, etc.)
- While methods are essentially the clients way of running code on the server with a Transverse module, events are essentially the server’s way of running code on the client, with the exception that the server can’t get returns from the functions, in which case you’d want to use Signal instead for that specific invoking to the client.
- service:Ready()
- Run this after you’ve added all your methods, events, and properties, and it’ll make so other scripts that try getting this service with Transverse.GetService() will actually be able to access the service now.
- If you’re going to do stuff with the service after finishing its completion, you’ll also want to update the service variable using the Transverse.GetService() method with the name you provided in Transverse.CreateService(), as the old variable you have from creating the service is “outdated”, you could say, and doesn’t contain all the methods and whatnot that are added to the return table of Tranverse.GetService().
- service:CreateProperty(propName: string, serverClientIndex: number, initValue: any)
- You don’t need properties for a whole lot, but they do have a few specific use cases that could come in handy, for example:
- You can make properties changeable by clients. This doesn’t mean the client can change the property for everyone, but it does mean the client can change the property locally. This could be really nice to set up client-adjustable settings or some other client-specific properties related to whatever service you’re creating.
- It provides an easy way to store information that both the client and server can see without having to manually create values (it does it with a combination of values and attributes I believe, though that’s not necessary to know to use them).
- In the case of properties, there is no client-only option (as in the serverClientIndex can only be either 1 or 2), as the server will pretty much always need the ability to adjust the properties, but 2 should work great for properties that are meant to be mostly local but start out with a default value.
- You don’t need properties for a whole lot, but they do have a few specific use cases that could come in handy, for example:
- Transverse.CreateService(name: string): service object
Example Usage
Server
Now this example isn’t anything practical of course but hopefully it gives you somewhat of an idea of how it works syntactically.
Conclusion
And that about concludes the Transverse module. You can get by just fine without using this module at all, but this module does have a few nice points to it and the syntax is really nice to work with, given that it works basically the exact same as an actual service does, syntax-wise.
Utility Modules
Show/Hide Content
Alright, now on to the rest of the modules in the “Utilities” folder of the framework. There’s the Event module, the Janitor module, the StringUtil module, and the TableUtil module. I’m just going to rapid fire explain each of these, as they’re just small tools and, well, utilities that you can use throughout in your other modules.
Event
Description
This is useful to use in object-oriented modules that you want to have custom event objects, similar to Roblox’s event objects.
Functions
This is an object-oriented module that creates a new “Event” object.
- Event.new() – creates and returns a new event object
- Event:Destroy() – destroys the current event object, disconnecting all connections
- Event:Connect(callback) – connects a function to the event and returns a function that, when ran, disconnects that connection (by removing it from the Event.ConnectedFunctions list)
- Event:Fire(…) – fires the event, running all connected functions in a task.spawn()
- Event:Wait() – waits for a single fire of the event, returning the values returned from :Fire() and disconnecting the connection after the first fire
- Event:Disconnect(callback) – if you store the connected function in a variable, you can disconnect that function using this method, but you probably won’t ever really need to since you can also do so just using the return function of Event:Connect()
Janitor
Description
This is useful for keeping track of a variety of tasks that need to be completed all at once, such as cleaning up after a special ability’s move or anything else really. It can also be useful for cutting off loose ends in an object’s :Destroy() function, as can be seen in the BaseClass template module.
Functions
This module is also object-oriented but comes with an additional “ClearParticles” function which is useful for clearing out visual effects models without cutting ParticleEmitter lifetimes short. It’s similar to various “Maid” modules I’ve seen, but with less sexist of a name.
- Janitor.new() – Creates and returns a new Janitor object
- Janitor:GiveChore(chore, …) – Adds a chore to the janitor’s list of chores. A chore can be any of the following:
- A RBXScriptConnection – will be disconnected upon cleaning
- A Tween object – will be :Cancel()'d upon cleaning
- A function – will be called with provided arguments through the “…” if there are any
- An AnimationTrack – Will be :Stop()'d upon cleaning
- Anything else that has a :Destroy() function, such as an instance or a custom OOP object – Will be :Destroy()'d upon cleaning
- Janitor:Clean() – Cleans all the chores, using the methods mentioned above
- Janitor:Destroy() – Runs Janitor:Clean(), but just allows you to add a janitor as a chore to another janitor
StringUtil
Description
This module mostly contains a number of useful functions, but can be potentially useful in other contexts as well.
Functions
- StringUtil.NumberString(num, roundTo) – returns a number formatted as a string rounded to the specified xths place but also abbreviated from K = 1000 to De = 1 decillion (i.e. 5.32 Mi)
- StringUtil.ConvertString(str: string) – Converts the string to a variety of other object types depending on the context. This is useful in commands and other things that require user input for non-string arguments, allows for the converting of strings to any of the following:
- booleans
- numbers
- tables (given that there’s no spaces)
- Vector3s
- objects that are a descendent of
game
(i.e. if the string was “game.Players.Knineteen19”) - If the string couldn’t be converted to any of those types, it’s simply returned back as a plain string
- All other functions are simply used as part of StringUtil.ConvertString() but can be used individually as well
TableUtil
Description
This module only has a single function as of right now
Functions
Here’s a list of all the current functions:
- TableUtil.DeepCopy(table) – Returns a deep copy of a table (table.clone() can be used for shallow copies)
- Is configured to cut cyclic tables short (in theory, anyway) so it doesn’t repeat infinitely.
Extra
I forgot to mention Deity also comes with a built in data manager for client/server using profile service aswell as replica service so thats a nice touch
Credits
Finally, the credits, HUGE HUGE credits to @Knineteen19 and anyone else whos stuff I am using for this, this framework honestly wasn’t meant to be released since I just used it in private projects as a base but might as well, I have removed alot some stuff Blu has as it honestly was not of need to me but yeah I hope you guys can use this pretty meh module base thingy but yeh have fun.