v2.0.1 Release
INTRO
Have you ever hated that you can’t make classes in roblox? Or rather, that you have to do weird work arounds to get one to work? Well worry no more! Presenting, OOPer’s Dream. A simple module/package that will allow you to create nice and organized classes, with class extending, inheritence, static and nonstatic methods, public and private methods, and properties. There’s even a plugin with a nice gui to manage all your classes and methods! Simply put the folder under replicated storage and get coding! Tutorial below.
HOW DOES IT WORK?
This will assume you have a vague knowledge of Classes and Objects in other programming languages. Once you import the folder into ReplicatedStorage, you will have a folder called “Classes” with a “ClassManager” and a “ReadMe” inside it. To make a class, simply make a new folder in the Classes folder called whatever you want the class to be called. It’s as easy as that to make a new class! To add a method, simply add a module script inside this new folder, and name it what you want the method to be called. In the module script, make it so it returns a function that takes two parameters, self and “data”. You don’t have to call them these, I just like to ;). It should look like this:
return function(self, data)
-- some code
end
Lets say we’re making a method called “PrintSomething”. Then, you can simply name the module script that. Then, lets say we want it to take a string parameter, then print that out. To take in that parameter, we add a string parameter to our function, and then in the function, we have it print. The code would look something like this:
return function(self, data, printString)
print(printString)
end
Now to test it, run the game, require the module ClassManager
, call GetClass
on what it returns, passing the name of the class you made, run .new() on the class you got to create an object of that class, then run .PrintSomething("Hello, World!")
on the object you made. Here’s what that would look like if we called our class “ExampleClass”:
local ClassManager = require(game.ReplicatedStorage.Classes.ClassManager)
local ExampleClass = ClassManager.GetClass("ExampleClass")
local exampleObject = ExampleClass.new()
exampleObject.PrintSomething("Hello, World!")
Sure enough, this prints out
Hello, World!
By creating module scripts, we can create methods on a class that we can create objects of at will! And because each method is in its own module script, it forces you to be organized! Under the TUTORIAL section, you’ll learn how to use the self and data parameters, use special methods, make classes inherit from other classes, make static/private methods, and make properties.
EXAMPLE USE
You may be wondering, when could this ever be useful? Well, I can think of a number of ways! Let’s say you’re working on an RPG game, and you want to make an item system. Well, you’re in luck! Let’s start by making an item class. Make a folder called “Item” under Classes.
Now we can start adding methods. Let’s say we need the methods drop and use, and to run some code when the item is made. Simply add three module scripts called __init__, use, and drop, and make them return a function that takes the parameters self and data. On __init__ we can also make it take the player that picked it up. In __init__, we can set a value on the object called “owner” for example to the player that picked it up. This would make our __init__ module script look like this:
return function(self, data, player)
self.owner = player
end
And lets say that in drop, we want to set that value to nil. The module we made called “drop” would look something like this:
return function(self, data)
self.owner = nil
end
Finally, lets have use print out that the owner of the item used it. The module called “use” could look like:
return function(self, data)
print(self.owner.." used this item!")
end
Great, now we have a working item class! To test this, we can put the following code into a serverscript and run the game:
local ClassManager = require(game.ReplicatedStorage.Classes.ClassManager)
local ItemClass = ClassManager.GetClass("Item")
local item = ItemClass.new("ianfinity01")
print(item.owner)
item.use()
item.drop()
print(item.owner)
This creates an item object thats owner is “Ianfinity01” (you can make this an actual player instead of a string if you want). It then prints the owner value of the object, uses the item, drops the item, then prints that value again.
Sure enough, we get
ianfinity01
ianfinity01 used this item!
nil
Great! Now, let’s say there are two types of items, weapons and food.
Lets create two more classes for these: “Weapon” and “Food”. Now we have:
Since weapon and food are subclasses of Item, go onto both the folders, and add a string attribute called “Extends” and set it to “Item”
Weapon and Food now inherit from Item! Now lets overwrite the use method. Put a module in both Weapon and Food called “use”
In weapon’s use, let’s make it take a target and print out that the owner of the item attacked the target. weapon’s use will look like this:
return function(self, data, target)
print(self.owner.." attacks "..target)
end
In food’s use, lets make it print that the owner of the item ate the item, then drop the item.
return function(self, data, target)
print(self.owner.." ate this item!")
self.drop()
end
Notice that we can call the drop method, even though Food doesn’t have a drop method. This is because it inherited it from Item. It also inherited __init__, meaning the object’s owner will be set. Now, lets do something special whenever you drop a weapon. Add a drop module to the Weapon folder. Lets have it say “player laid down their weapon” before doing what it would normally do when an item is dropped. This is where that data parameter finally comes into play. The drop method of weapon will overwrite the drop method of Item, but using data.getParentMethod()
, we can get the method weapon’s drop method overwrote (item’s drop method). Weapon’s drop method code would look like this:
return function(self, data)
print(self.owner.." laid down their weapon!")
local drop = data.getParentMethod()
drop()
end
This prints that the owner of the weapon laid down their weapon, then it gets item’s drop method and calls that as well.
Lets test this.
local ClassManager = require(game.ReplicatedStorage.Classes.ClassManager)
local WeaponClass = ClassManager.GetClass("Weapon")
local FoodClass = ClassManager.GetClass("Food")
local weapon = WeaponClass.new("ianfinity01")
local food = FoodClass.new("ianfinity01")
food.use()
print(food.owner)
weapon.use("bugs")
weapon.drop()
print(weapon.owner)
This should create a food and a weapon held by ianfinity01, then eat the food (which drops the food), print the food’s owner (should be nil because we dropped it). Then, it will attack “bugs” with the weapon, drop the weapon, and print the owner of the weapon (should also be nil because it was dropped). Sure enough, we get:
ianfinity01 ate this item!
nil
ianfinity01 attacks bugs
ianfinity01 laid down their weapon!
nil
If you’re confused why weapon’s owner still gets set to nil even though the drop method was overwritten, it’s because we manually ran the method it overwrote by calling the function returned by data.getParentMethod()
. We could take this further by adding other item types like potions, or adding specific weapons like bows, swords, and daggers that extend the weapon class, but this should give you an idea of how this module works, so we’ll stop here for now. Feel free to mess around with this on your own!
EXAMPLE USE WITH PLUGIN
Here, we’re going to do the same thing we did in EXAMPLE USE, but using the OOPer’s Dream plugin to make things a bit easier. I suggest still reading the EXAMPLE USE section. Let’s say we want to make an item system for an RPG game. First, open up the plugin. You’ll see a filter classes box and a empty box.
Right click on the empty box and press Create New Class
. Now you have a new class! Select the class and either press F2, or right click on it and press rename to rename it. Let’s rename it to “Item”.
If you double click on the class or press enter, it will open another window for that item class.
Press the + on methods and rename it to “__init__”. Repeat this process to also create a “drop” and a “use” method.
You can double click or press enter on a method to edit it. __init__ will run when the object is created, passing any parameters that are passed on the object’s creation. Let’s edit __init__. Right away, we’ll see this:
return function(self, data)
end
Let’s have it so you pass the owner of the item, and store that under item.owner.
We will end up with this:
return function(self, data, player)
self.owner = player
end
Now, under drop, let’s have it set the owner to nil. It will look like this:
return function(self, data)
self.owner = nil
end
Now, lets say under use, it prints out that the owner of the item used the item. It will look like this:
return function(self, data)
print(self.owner.." used this item!")
end
Now, lets test this. Put the following into a script and run the game:
local ClassManager = require(game.ReplicatedStorage.Classes.ClassManager)
local ItemClass = ClassManager.GetClass("Item")
local item = ItemClass.new("ianfinity01")
print(item.owner)
item.use()
item.drop()
print(item.owner)
This should create an Item class, print the owner, use the item, drop it, then print the owner again. However, since we dropped it, the owner should be nil the second time. Sure enough, we get:
ianfinity01
ianfinity01 used this item!
nil
Now, let’s take this a step further. Let’s create a weapon class and a food class that are subclasses of item. In the classes gui, hover over Item and press the plus icon twice. Rename the two classes you created to “Weapon” and “Food”.
Now let’s edit Food. Double click on it or press enter with it selected. You should see this:
The arrows mean the class is inherited from a parent class, and to the right of the name, you can see that they are all inherited from item. Since Food is a subclass of Item, it inherits Item’s methods. Now, let’s overwrite the use method. Right click on use and press overwrite. Now, use has an O next to it. This means its overwriting a method it inherited. If you edit use, you’ll see this:
return function(self, data)
local overwrittenMethod = data.getParentMethod()
overwrittenMethod()
end
We do not need everything inside the function, so just delete that for now.
return function(self, data)
end
Now, lets make it so that it prints that the owner of the item ate it, then drops the item. Food’s use method should now look like this:
return function(self, data)
print(self.owner.." ate this item!")
self.drop()
end
Great, that’s the Food class. Swapping over to the weapon class, let’s overwrite use. Let’s make use take an additional parameter of who the weapon attacks. Then, let’s have it print who it attacks. Now, Weapon’s use method looks like this:
return function(self, data, target)
print(self.owner.." attacks "..target)
end
Finally, let’s modify the drop method. Right click the drop method and press overwrite. This time, let’s not delete everything it gives us, and instead add something before it. Lets have it print that the owner of the item laid down their weapon. Now, Weapon’s drop method looks like this:
return function(self, data)
print(self.owner.." laid down their weapon!")
local overwrittenMethod = data.getParentMethod()
overwrittenMethod()
end
You’re probably wondering what the code we left in does. It calls the getParentMethod()
function of data, which returns the function that this method overwrote. Since weapon’s drop method overwrote item’s drop method, this will return item’s drop method. Then, we call that function. So basically, it just makes it so that instead of running this new drop method instead of the item drop method, it just runs it before the item drop method. Let’s test out everything we’ve done so far. Put this code into a serverscript and run the game:
local ClassManager = require(game.ReplicatedStorage.Classes.ClassManager)
local WeaponClass = ClassManager.GetClass("Weapon")
local FoodClass = ClassManager.GetClass("Food")
local weapon = WeaponClass.new("ianfinity01")
local food = FoodClass.new("ianfinity01")
food.use()
print(food.owner)
weapon.use("bugs")
weapon.drop()
print(weapon.owner)
This will create a weapon and a food that belong to “ianfinity01”. Then, we use (eat) the food which should also drop it. Then, it prints the owner of the food, which should be nil because it was dropped. Then, it uses the weapon on “bugs”, and drops the weapon. It then prints the weapon’s owner which should be nil because it was dropped. Sure enough, we get this output:
ianfinity01 ate this item!
nil
ianfinity01 attacks bugs
ianfinity01 laid down their weapon!
nil
Recall that the weapon’s owner was set to nil when it was dropped because the item’s drop method was manually called in the weapon’s drop method. We could go a lot further with this by adding other item types like armor or potions, or we could make bow, sword, dagger, etc sub classes to weapon, but for the purpose of this mini-tutorial, let’s just leave it there. I hope this gave you the tools you needed to start making your own classes!
TUTORIAL
Let’s say you want to create a class. All you have to do is create a folder under the Classes folder in ReplicatedStorage. If you have the plugin, just right click on the empty box in the gui and press Create New Class
. To access the class ingame, you can require the ClassManager module in the classes folder, and run .getClass(className) on the required module where className is the string name of the class. If you want to make the class a sub class of another class, add a string attribute to the folder called “Extends” (capital E) and set it to the name of the class you want it to inherit from. In the plugin, right click on the class and press Change Parent
. To add a method, add a module script to the folder. Name the module script what you want the method to be called. Make it so that it returns a function that takes self and data as parameters. You can make it take additional parameters after self and data. Those must be manually passed when calling the method. In the plugin, double click on or press enter on the class you want to add a method to. Then, in the gui that opens, press the plus next to Methods to add a method. You can double click on it or press enter to edit it. You can right click and press rename, or press F2 on the method to rename it. In a method, self is the object on which the method was called, and data is a object that contains some extra information about the object on which the method was called, but we’ll get into that later. You can make a method private by adding a boolean attribute called “Private” on the method’s module script and setting it to true. On the plugin, right click on the method and press Make Private
. To make a method static, add a boolean attribute on the method’s module script called “Static” and set it to true. On the plugin, right click on the method and press Make Static
. A method CAN be both Private and Static. A private method can only be accessed using data (I’ll tell u how later). A static method can only be accessed on the class, not an object. Self will be the class on which it was called for static methods.
Suppose we have a class called “ExampleClass” and ran the following code:
local ClassManager = require(game.ReplicatedStorage.Classes.ClassManager)
local class = ClassManager.getClass("ExampleClass")
local object = class.new()
If we had a public non-static method called exampleMethod, we could do object.exampleMethod()
. However, if it is static, we must do class.exampleMethod()
instead.
If a method “privateMethod” is private, it must be accessed using data (the second parameter of all methods). This is so that it can only be accessed within the classes’ methods. If you have a non-static private method, within another non-static method, you can do the following:
local privateSelf = data.getPrivateObjectData()
privateSelf.privateMethod()
If you want to store private variables, you can also do this:
local privateSelf = data.getPrivateObjectData()
privateSelf.variableName = someValue
Then you can access that variable later using the data parameter of another parameter on the same object. For example, to print it, you could do this:
local privateSelf = data.getPrivateObjectData()
print(privateSelf.variableName)
You can only do data.getPrivateObjectData()
on non-static methods. If you have a private static method called “privateMethod”, it can be accessed using this:
local privateClass = data.getPrivateStaticData()
privateClass.privateMethod()
You can similarly store data on privateClass
. Remember that .getPrivateStaticData()
, unlike .getPrivateObjectData()
, returns data specific to the entire class, not specific to any one object of that class. The last thing data is good for is accessing overwritten methods. Let’s say that you have a public static method called “someMethod” on a class “Class1”. Let’s also say that you have another class “Class2” that extends Class1. If Class2 also has a public static method called “someMethod”. Class2’s someMethod will overwrite Class1’s someMethod if you call Class2.someMethod()
Let’s say you want to run Class1’s someMethod in Class2’s someMethod. You can do data.getParentMethod()
to get the method a method overwrote. So, if Class1’s someMethod looks like this:
return function(self, data)
print("a")
end
And Class2’s someMethod looks like this:
return function(self, data)
local superMethod = data.getParentMethod()
superMethod()
print("b")
end
And you run Class2.someMethod()
, it will print this:
a
b
This is because in Class2’s someMethod, it calls Class1’s someMethod (which prints a), then prints b.
If you prefer calling methods with a :
instead of a .
in your code, you can enable this by setting an attribute on the class folder called “UseColons” to true. On the plugin, right click on the class and press “Use Colons”. The class will work identically except all methods must be called with a colon. In our previous example, you would run Class2:someMethod()
instead of Class2.someMethod()
. Keep in mind that enabling UseColons will also make built in functions like getClassName and IsA use colons as well.
As a tool to keep your code clean, you can also use properties, which are like variables that have set code to determine what is returned when reading from them and to determine what happens when writing to it. To make a property, make a module script and add an attribute to it named “Property” and set it to true. On the plugin create a new method, right click on it, and select “Convert to Property”. How these work is like the following
return function(self, data, set, val)
end
set is a boolean of whether or not it is being set to (or read from if set is false)
val is the value that it is being set to. If set is false, this will be nil.
if set is false, the value the function returns is what the property is read as.
For example, if you had the following code on a non static public property, you could read and write to the value as normal, but have prints telling you whenever the value is written to/read from
return function(self, data, set, val)
local privateSelf = data.getPrivateObjectData()
if set then
privateSelf.testingVal = val
print("Set property to"..tostring(val))
else
print("Read property (value is "..tostring(val)..")")
return privateSelf.testingVal
end
end
If the aforementioned module script (property) was named “ExProp” then you could do the following:
obj.ExProp = 5
print(obj.ExProp)
giving the following output:
Set property to 5
Read property (value is 5)
5
Properties are great if you need to limit what a value can be set to, or want to fire events/run code when the value is changed, but dont want to have a get method and a set method.
I hope this helps you use my module!
DOCS
Special Methods: if a method is named this, it will have special properties as said next to it.
__init__
This can be static or non-static. If it is static, it will run when the class is created. If it is non-static, it will run when an object is created. In its static form, its parameters will only be self (the class) and data. In its non-static form, its parameters will be self (the object), data, and anything else passed in the class.new()
. In its non-static form, it may also return a value. If nothing is returned, or the returned value is nil, calling .new() will return the created object as usual. However, if it returns a non-nil value, .new() will return the returned value instead of the created object. This method cannot be called manually but will be called automatically. Keep in mind that you must manually call overwritten __init__
methods using data.getParentMethod()([params])
__negate__
This is only non-static. Its parameters will be self (the object) and data. Whenever you do -object
, it will produce whatever this method returns.
__add__
This is only non-static. Its parameters will be self (the object), data, objectA, and objectB. One of objectA or objectB will be self, but the other can be anything. Whenever you do objectA + objectB
where objectA or objectB is an object with this method, it will produce whatever is returned by calling this function.
__sub__
Like __add__
but whenever you do objectA - objectB
.
__mul__
Like __add__
but whenever you do objectA * objectB
.
__div__
Like __add__
but whenever you do objectA / objectB
.
__mod__
Like __add__
but whenever you do objectA % objectB
.
__pow__
Like __add__
but whenever you do objectA ^ objectB
.
__str__
This is only non-static. Its parameters will be self (the object) and data. Whenever you do tostring(object)
, it will return whatever this method returns.
__eq__
Like __add__
but whenever you do objectA == objectB
. Only works when comparing two objects of the same class, otherwise returns false.
__lt__
Like __add__
but whenever you do objectA < objectB
. If you do objectA > objectB
, it will return the result of this function, but swapping the order of the two parameters after self and data. Only works when comparing two objects of the same class, otherwise errors.
__le__
Like __add__
but whenever you do objectA <= objectB
. If you do objectA >= objectB
, it will return the result of this function, but swapping the order of the two parameters after self and data. Only works when comparing two objects of the same class, otherwise errors.
__len__
This is only non-static. Its parameters will be self (the object) and data. Whenever you do #object
, it will return whatever this method returns.
A class can extend (inherit from) another class by adding a string attribute on the child class’ folder that is set to the name of the parent class (case sensitive)
If you require the ClassManager module in the Classes folder under Replicated storage, it will have the following methods:
GetClass(className)
className is a string. Will error if there is a compilation error, or no class can be found with the name className. className is case sensitive. Returns the class with the name className
GetClassExtensionPath(className)
className is a string. Will error if there is no class with the name className, or if the class hierarchy is invalid. Returns a list of classNames where the first is className, the second is the name of the className’s parent class, the third is the parent of that parent class, and so on.
IsObject(object)
object can be anything. Will return true if it thinks it is an object of a class. Will return false if it thinks otherwise.
IsClass(class)
class can be anything. Will return true if it thinks it is a class. Will return false if it thinks otherwise.
The following methods will always be found on a class (unless manually overwritten)
getName()
Returns the name of the class.
The following methods will always be found on an object of a class (unless manually overwritten)
getClass()
Returns the class it was created from.
IsA(className)
className is a string. Returns if it is called an object of a class named className, or if the class of the object was called on is a descendant class of a class named className. If you had a class named Class1, and a class that extends Class1 called Class2, and you did Class2.new().IsA(“Class1”), it would return true. However, if you did Class1.new().IsA(“Class2”), it would return false.
getClassName()
Returns the name of the class it was created from.
The following methods are on the data object that is passed as the second parameter of all methods:
getParentMethod()
Returns the callable method the method overwrote. Returns nil if the method did not overwrite a method of a parent class.
getPrivateObjectData()
Only callable in non-static methods. Returns a table that contains all private methods of the object. Can also be used to store private variables. The self parameter of private methods will still be the public object (or the public class in the case of static methods).
getPrivateStaticData()
Can be called from a non-static or static method. Returns a table that contains all static private methods of the class. Can also be used to store private variables.
methods can be made static by setting an attribute “Static” to true on the module script
methods can be made private by setting an attribute “Private” to true on the module script
methods can be made into properties by setting an attribute “Property” to true on the module script
Properties should be in the format
return function(self, data, set, val)
end
where self and data are the same as always, but set is a boolean of whether or not you are setting to the property (or reading from it if it is false), and val is the value you are trying to set it to if set is true. If set is false, val will always be nil.
INSTALLATION
If you have the plugin, all you have to do is be in a place with the plugin enabled, and it will automatically install the module. You can get the plugin here: plugin. Make sure to enable script injection. If you do not have the plugin, insert this rbxm file into the ReplicatedStorage of your game:
oopersDream.rbxm (7.7 KB)
TROUBLESHOOTING
Here is a list of common errors you may encounter while the module tries to compile a class:
Error, className has an extension loop. Failed to compile.
This means the class “className” has an extension loop. An example of this is if you have a class Class1, and a class Class2, and Class1 extends Class2, while Class2 also extends Class1.
Invalid type of Class className's 'Extends' value. Must be the string name of its parent class or nil.
This means the class “className” has an attribute Extends, but the attribute is not a string or missing. The Extends attribute must either be a string, or not present.
Class className's' parent Class 'otherClassName' not found.
This mean’s the class “className” has its Extends attribute set to “otherClassName” but there is no class with the name “otherClassName”. Make sure to check your capitalization and spelling. If you do not wish for it to extend anything, either remove the attribute, or set it to an empty string.
ERROR WHILE COMPILING: error requiring method methodName of class className. Error message below:
This means the module script named “methodName” in the class “className” had an error while requiring. Examine the error below this message to fix the moduleScript.
ERROR WHILE COMPILING: incorrect return type of method methodName on class className. Must return a function.
Self explanatory. The method “methodName” of the class “className” did not return a function when required.
ERROR WHILE COMPILING: multiple methods with the same name (methodName) on class className.
This means that multiple methods have the same name (“methodName”) and the same visibility (same Static value and same Private value). Delete a module or change the name of one.
If you have an error that is not on this list that you believe is not your fault, please reply to this post.
IMPORTANT WARNINGS
Avoid naming any public non-static methods “IsA”, “getClass”, or “getClassName”. It will still compile, however it will overwrite important functions and is highly not recommended. Same thing goes for public static methods called “getName”. Avoid having multiple classes named the same thing. Never use rawset or rawget on a object or class as this may break properties or other things. Also never set the metatable of a class or object. You may use a class or object as a metatable, but methods (including colon functions) will be run with self as the class or object, ignoring its parent by __index, possibly causing unwanted behavior.
CHANGELOG
v1.1.0 initial release
v1.1.1 fixed special methods
v1.2.1 large bug fixes for plugin
v1.3.1 added the ability to return a value in __init__
.
v2.0.0 added the ability to make classes use colons, added properties, added bug fixes for when the init of a class is not instantaneous
v.2.0.1 hotfix to inheritance bug
If you have any problems or questions, please let me know in the replies!