BLU Framework Handler Plugin

Introduction

The BLU framework is a modular framework largely inspired by @hexadecagons (check the pinned comment here) FreeBird framework but with additional features and functionality I’ve added over time. It creates a workflow similar to that of Unity, but within Roblox Studio, adding 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.

The Plugin

Plugin Link:

Updates

Show/Hide Content

1/20/2024

  • Updated Signal module to include alternate functions for UnreliableRemoteEvents. Listening to UnreliableRemoteEvents works the same as with regular RemoteEvents and RemoteFunctions.
  • Some adjustments to StringUtil.NumberString() to allow specification of significant figures to display.
  • Added PlayerUIModule to template modules.

11/18/2023

  • Added TableUtil.LoadTableOntoTable() function. This is particularly useful when loading data from a datastore, as it loads all values (recursively) but ignores the ones that aren’t already on the “loadTo” table.

11/12/2023

  • Made the plugin free to download / open source
  • Fixed a bug with TableUtil.DeepCopy that stopped ModularBlue from working on the server side when cyclic references existed
  • Added a TableUtil.MultiLayerShallowCopy() function (mostly in desperation trying to fix the issue above, but hey it exists now)

9/24/2023

  • Added time formatting to StringUtil module
  • Fixed an issue in the Inherited Class template module

9/10/2023

  • Changed RunService events to match the new events shown in the documentation:
    • __Stepped changed to __PreSimulation
    • __Heartbeat changed to __PostSimulation
    • __RenderStepped changed to __PreRender
  • Added __PreAnimation as well from RunService.
    RunService | Documentation - Roblox Creator Hub

9/7/2023

  • Added Transverse service template module

9/6/2023

  • Plugin was released

Framework Set Up

Show/Hide Content

image

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 modules to this framework, you can simply press the “Set Up” button and move your modules around until they’re all required 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 BLU framework in order to make sure it never deletes any of your own objects or code, but that may mean you have to manually move modules over if removing and setting up again creates a duplicate of a BLU script.

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 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 in ReplicatedFirst will be require()'d before other modules.

  • Modules will only be required if they exist in a variety of specific locations:

    • Shared locations:

      • ReplicatedFirst.Modules (folder)
      • ReplicatedStorage.Modules (folder)
    • Server locations:

      • ServerScriptService.Server (server script)
      • ServerStorage.Modules (folder)
    • Client locations

      • StarterPlayer.StarterPlayerScripts.Client (local script)

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.

  • Modules in ReplicatedFirst will be initialized first as well.

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
  • 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

Creating a Basic Module

Show/Hide Content

This plugin 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.
image

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.

In our new “Controls” module, we’ll create some values inside the Controls table, which will essentially be like “public variables” if we were to compare this to Unity, and we’ll use these values to store some basic keybinds that we’ll check for in a moment.
image

The reason I say these values are similar to “public variables” in Unity or other programming languages is because they can be seen and changed from other modules on the client (given that this module is in a client location and is therefore running on the client). This will be useful in the case that you wanted to implement keybind customization, but it can also be dangerous if you’re storing values you wouldn’t want an exploiter to be able to change (in the case of a local module, anyway).

Anyway, we’ll use the built-in :__InputBegan() event to check input for each of these keybinds.

And it’s as simple as that. We can also remove the :__init() function since I don’t think we’ll be using it in this module.
image

And as you can see, it works as expected as well: https://gyazo.com/f48a0c515a79e3078e5f20336f5cac7e

We’ll go ahead and add some debounces and cooldowns though too, just to make it more realistic to something you might actually make in a project.

https://gyazo.com/8516986b3f0a5922ce9dec8cdda2c098

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.

We’ll create a module in the Server script to go along with out “Controls” module and call it “Ability”.

In both the “Controls” module on the client and the “Ability” module on the server, we’ll create a variable called “Signal”, which we’ll set to the signal module, but using _G.Require() in place of the normal require()
image

_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.
  • 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.
  • 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, and is used in another module of mine called “Replicator”, which is separate from the BLU framework.
    • Signal.InvokeClient(player, key, …)

      • Works the same as RemoteFunction:InvokeClient()

Again, credit for this module goes almost entirely to @Hexadecagons as this is simply my own modified version of one of their modules from a project we both worked on.

Using Signal for Client/Server Communication

Now, in place of our print from earlier, we’ll run the Signal module’s Signal.FireServer() function with a custom key that we’ll remember in order to listen to the fire on the server module.
image

image

And on the server, we’ll listen to those remote fires (the key changes with each moveInt) using Signal.ListenRemote(), connecting each one to a new corresponding function.

And finally, we can test it and see the prints appear on the server this time instead of the client.
https://gyazo.com/6af0d1f59d4156c311700e9e0fdba4cd

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.
  • 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.

Example Usage

Server

Client

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 (me crying tears in the background because this module took me forever only for me to immediately find something way more useful in another person’s framework, FreeBird), 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.

MovementHandler

Show/Hide Content

Introduction

So MovementHandler was something I came up with to combat an issue I had come across in several projects up until that point: having multiple scripts and abilities influencing a player’s WalkSpeed or JumpHeight/JumpPower at the same time, causing issues like making the player faster or slower than intended. The goal of this module was to completely eradicate that issue, and I so far I’d say it’s worked wonders.

The Run-Down

The nice thing about this module is that, while it is, at the core level, set up in an object-oriented fashion, with a “MovementHandler” object for each character and a .new() function for creating new instances of this object type, all the creating instances for each character is handled automatically, and the only function you need to even worry about is the MovementHandler.AdjustMovement() function. You don’t even need to worry about the MovementHandler.RemoveAdjustments() function or any others because .AdjustMovement() already returns a function you can call to remove any WalkSpeed/Jump adjustments you add.

As opposed to finding ways to manually play with WalkSpeed or JumpPower/JumpHeight, you can add what are called “adjustments” to a character’s “movement” (jump or walk) that influence how fast they move or how high they jump automatically in an update loop (which runs on RenderStepped on the client and Stepped on the server).

If you were to add multiple adjustments to the same character, the code would automatically default to the lowest final value provided for both Jump or WalkSpeed, individually. It also accounts for whether to manipulate JumpPower or JumpHeight automatically based on your settings in StarterPlayer as well. You would normally want to add these adjustments from the client, but if doing so is inconvenient, there’s also the ability to do so (using the same exact function) from the server, but that involves a RemotEvent fire, and so the player’s speed will actually be adjusted a bit after running the function, when the message reaches that specific client that the character corresponds to.

Usage

So let’s say we had a local script for sprinting. We’ll insert a new template “Character Module” and define our sprinting keybind as a “public variable”, by adding it to the return table. We’ll also create a constant variable for the sprinting speed, and set up the input using our built-in event methods. Finally, we’ll create a Sprinting:Start() and Sprinting:Stop() function which will serve as our start and stop functions to toggle sprinting (though the keybind will be hold to sprint, rather than press to toggle).

-- Services
local Players = game:GetService("Players")

-- Modules
local MovementHandler = _G.Require("MovementHandler")

-- Player Variables
local player = Players.LocalPlayer
local char, hum, root = nil, nil, nil

-- Constants
local SPRINT_SPEED = 25

-- Private Variables

-- Module
local Sprinting = {}
Sprinting.Keybind = Enum.KeyCode.LeftShift

-- Character added
function Sprinting:__CharacterAdded(_char, _hum, _root)
	
	-- Character variables
	char, hum, root = _char, _hum, _root
	
end

-- Character died
function Sprinting:__CharacterDied(_char)
	char, hum, root = nil, nil, nil
end

-- Input began
function Sprinting:__InputBegan(input, gameProcessedEvent)
	if gameProcessedEvent then return end
	
	if input.KeyCode == self.Keybind then
		self:Start()
	end
end

-- Input ended
function Sprinting:__InputEnded(input, gameProcessedEvent)
	if input.KeyCode == self.Keybind then
		self:Stop()
	end
end

-- Starting sprinting
function Sprinting:Start()
	
end

-- Ending sprinting
function Sprinting:Stop()
	
end

-- Returning
return Sprinting

Now we’ll set up the :Start() and :Stop() functions using the movement handler.

-- Starting sprinting
function Sprinting:Start()
	if not char then return end
	stopSprinting = MovementHandler.AdjustMovement(char, 
		{"WalkAdjustments", "Set", SPRINT_SPEED}
	)
end

-- Ending sprinting
function Sprinting:Stop()
	if stopSprinting then
		stopSprinting() -- removes the adjustments
	end
end

And just like that, it works perfectly, at least aside from any animations or anything else you might want to add to it.
https://gyazo.com/dddf0a4eb771ee6c70a01877dfa76c08

So if you’re still confused how MovementHandler.AdjustMovement() works, allow me to give a quick debrief to explain what’s going on here. This function takes a character model as the first argument, and can take up to infinitely many extra arguments, which are intended to be tables, formatted like I did up above.

Each “adjustment” (the tables you provide after the character, like {"WalkAdjustments", "Set", SPRINT_SPEED} from earlier) has three main values to be aware of. The first value is the “type” of adjustment, which can either be “WalkAdjustments” or “JumpAdjustments”. The second value is how it adjusts, which can either be by setting it directly to a specific value using “Set” or by multiplying the value by a number using “Multiply”. The final value is the value to set or multiply it to.

You can add as many adjustments as you want in one function call, but you’re probably not going to want any more than two (one for walk, one for jump) in a single given call. For example, the code to temporarily stun a player might be like so:

local unfreeze = MovementHandler.AdjustMovement(char,
	{"WalkAdjustments", "Set", 0},
	{"JumpAdjustments", "Set", 0}
)

task.delay(5, unfreeze)

What’s useful about doing this instead of just setting WalkSpeed and JumpHeight is that it will never interfere with anything else that adjusts WalkSpeed and JumpHeight. For example, if you tried running while this stun adjustment was added, the MovementHandler would still take the lowest value, 0 for each, and any other adjustments would be essentially ignored until that stun adjustment was removed, in which case it’d then default to the next lowest value for each, or the StarterPlayer default values if there are no adjustments specified.

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, most of which are used by ModularBlue, 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, but it’s also used in the ModularBlue modules.

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.
6 Likes

its useless for 1K robac
its more easy to do everything yourself than bying this plugin
setting up framework isnt that hard as you may think
you just need some skill and patience

1 Like

8 Likes

1k Robux is only like $3.5 when it reaches me. And yeah, sure, people can make their own frameworks, but those aren’t the people I’m advertising to, so what’s your point? You say the framework is useless, but you haven’t even tried it. I’ve tested this framework across a wide variety of projects and games and it has greatly accelerated my development, so I simply wanted to share it and maybe help pay for college at the same time. Is there something so wrong with that? If you don’t want it, don’t buy it. No one’s forcing you after all. There’s no reason to post a hate comment just because you don’t like it.

Edit: If you’re basing the framework off the video I posted at the top, don’t. Like I said, that video is very outdated and doesn’t include half the features I’ve added since then, not to mention the 3 or 4 useful modules I’ve included in it as well.

3 Likes

What’s this about? I’m confused what the image is for.

It’s a joke about the name of the plugin.

2 Likes

Ah okay. Sorry I didn’t get the reference.

Pretty cool keep up!
If it supports vscode and a good intelisesne I will sure use it

1 Like

Even though this is a joke, I feel like this is a good idea to use it as the plugin’s logo

2 Likes

Only issue is stuff like copyright . . .
Could be kind of funny though. Though I guess if we’re talking copyright, maybe “Built-Like Unity” isn’t the best name anyway. I could just say it doesn’t stand for anything, since “BLU” is similar to an old username I used to have on this platform anyway lol.

Either way, I might make the plugin free since I want to start using it in my tutorial videos but I’d feel bad making tutorials that people would have to pay to follow since all of my other tutorials are completely free to the public. I’ll update this page once I make those changes . . . probably.