#Making Tools (and Others) with OOP

1 messages · Page 1 of 1 (latest)

acoustic ledge
#

Heya, still fairly new to scripting but have already coded a fair bit for my game. Now I'm just wanting to use OOP practices after my FYP page absolutely blew up with it. I've done a fair bit of research when it comes to OOP in Roblox, but honestly...nothing is useful. Here's some more details to my dilemma.

I've already learned OOP in university, and have used it comfortably well in Python and Java. So much so, if I'm working in a multi-paradigm language like Python, I always opt for OOP. That means I know: Classes, Object instances, attributes, class methods, constructors etc. I also know certain basic level concepts like Inheritance, Polymorphism, Encapsulation etc.

The problem however, is I don't know how to use it in the context of Roblox (and game design in general), specifically how to use it practically. It's even more difficult since Lua is functional by design, and only provides the mechanisms for OOP, which makes some implementations unnecessarily more complex and unintuitive. And most of the videos and articles I've watched and read don't really touch on this, since they're so focused on teaching the absolute basics of OOP of which I already know.

So here's my question
Currently, I have a non functional flashlight model mesh. It's just the model of a basic flashlight i grabbed from the asset store, and removed whatever script (since I want to add my own functionality) it had.

So how do I add basic functionality to my flashlight model (on and off) using OOP? How do I structure my files accordingly?

I've tried (and failed) to create classes (one item class, and a flashlight that inherits from the item class). But I don't understand how do I actually take my class, and apply it to my model, so it functions as an Object. So if I've created a flashlight class, how do I take my class, and somehow apply it to my flashlight model so the flashlight can actually turn on and off, using OOP?

One final thing: I'm not really asking for you to write a script for me or something like that. If i didn't want to use OOP, I could code this thing in a flash, easy peasy. Since I'm more interested in learning as opposed to having the final result, I'd just like an explanation of how to approach this.

Consequently, if you decide to answer or help me with this, know that unless you give me a super detailed answer or explanation (you don't have to, but i welcome it), I will probably annoy the hell out of you with follow up questions, so just be warned :)

lucid bronze
#

what the fuck is this

warm dune
#

wth is OOP

lucid bronze
lucid bronze
#

Did you read any devforums

acoustic ledge
# lucid bronze next time wehn you make a thread could you explain it in one sentance I didnt ne...

would you rather ask me 20 follow up questions, or have me give you the details all in one go? If i was the one answering the question, I'd rather not have to ask 20 basic questions they could've given to me when answering the question.

For example, you wouldn't need to ask if I've read any dev forums since my initial message already explained that I did, and why they weren't helpful. The one you linked I've also read, and while somewhat helpful, was still confusing.

Lastly, the onus is not on you to answer, if you didn't feel like reading or answering, could simply ignore it. No hostility intended

vivid shuttle
#

Yea, well i will do an ecapsulation of the Model flashlight. For instance roblox donest have flashlight instance then you do your own with OOP. for instance in roblox a intance tool have events (Activated, Equipped, Unequipped etc) but thos is not inheiretly binded to any function, because roblox doesn't know what tool you are doing. That said make a class which take in Props and retrun a table with setmetable to it self with mehtods like :On(), :Off(), or make it bind to a "static" function like activated (close light souce) and more, This is only one of many was of using OOP

vivid shuttle
#

So like you onlu have to write like "local OneFlashLight = FlashLight.new(Model, ...)" and when constucting the instance it can bind it to specific functions with the recived aguments. OOP can help full when dealing with more complicated stuff (Not neccecary Flashlight). So yea, instead of putting client script under a tool and clone it when needed you can use CollectionService and loop thru them with class or other

bleak obsidian
#

module script

#

each one looks smth like this

#
local module = {}

return module
#

for each module, either rename it to the class or don't, your choice. makes no functional difference.
but if you want class objects you have to add

module.__index = module
#

below module's (or whatever you have named it) declaration

#

just keep in mind, all code must be underneath the module's declaration and above the return statement, but since you know plenty about OOP already, you probably figured out that second part

#

basically modules are just functions in instance form

#

then the constructor method should look something like this

function module.new(param1:number,param2)
    local self = setmetatable({},module)
    
    self.data1 = param1
    self.data2 = param2
    
    return self
end
#

metatables allows us to use the self keyword in non-static class methods, you should be familiar with it since you use python

#

then those class methods will look something like this

#
function module:doThing(parameter)
    self.data1 *= parameter -- really any code you want, and you don't need to put self in the params like python
end
#

that's basically it.

#

you can have module scripts act as containers for packages of modules, you can treat module scripts like java classes, you can make subclasses (unfortunately missing some of the functionality present in java), you can really do anything. right now i'm working on a way to assign custom classes to instances and it's great.

#

hell you can even omit any oop in a module and just use it as a library

#

@acoustic ledge

#

sorry for the ping but forgot to reply to your first message with my first one

#

oh and almost forgot to add, very important unless you already know, you have to use

local myModule = require(moduleScript)

to use the module

acoustic ledge
# bleak obsidian oh and almost forgot to add, very important unless you already know, you have to...

Sorry for the late reply, and no problem about the ping. Thanks for the detail, though most of it I gathered from the information i researched. Metatable() method, and metatables in general is still a bit of a doozy for me (maybe better explanable if you could compare it to something familiar in oop?), but my surface level of understanding is that it goes in every constructor i make, and works something like a super() method? This definitely sounds wrong, and if it does please correct me, but some implementations I've seen they do something like setmetatable(child, parent).

acoustic ledge
bleak obsidian
#

allows you to use class methods, so it's more like just a bit of extra constructor method code

#

so the object you're instantiating actually belongs to that class

#

and isn't just any other table

bleak obsidian
acoustic ledge
bleak obsidian
acoustic ledge
# bleak obsidian you get an object of that class

Ok shit, this clicked for me. So i have objectOne = setmetatable({},Animal), and objectTwo = setmetatable({}, Animal), I'm assuming both variables contain a different object instance of the same class, Animal?

In the setmetatable() method though, I don't necessarily have to pass an empty table do I? If i passed something like setmetatable({value = true}, Animal), assuming animal doesnt already have an attribute called value, I would get an object of class Animal, but with the value from my original table as another attribute right? Wouldn't this work as inheritance?

bleak obsidian
#

so if you have a classless table, you can use setmetatable to assign it a class, really useful when it comes to remote events since classes aren't communicated between server and client and vise versa

so something like this

local animal = require(animalModule)

local myAnimal = {}
myAnimal.isStinky = true

setmetatable(myAnimal,animal)
myAnimal:eatGrass()

#

would work perfectly fine

#

but it's recommended to use setmetatable in the class script instead of outside wherever you can

vivid shuttle
#
local Stack = {};Stack.__index = Stack

export type Stack<T> = typeof(setmetatable(
    {}::{
        T
    }, Stack))

function Stack.new<T>(StartStack:{any}?):Stack<T>
    local self = setmetatable(StartStack or {}, Stack)

    return self
end

function Stack:Add(Value:any)
    table.insert(self, Value)
end

function Stack:Pop<T>():T?
    local Value = self[#self]
    self[#self] = nil
    return Value
end

function Stack:Size():number
    return #self
end

function Stack:Clear():nil
    setmetatable(self, nil)
    table.clear(self)
end
#

this is a typical way of writing class

#

would discribe setmetatable as giving abilityes to a table. like setmetatable({}, {}), the second argument should include/discribe the abilityies for the first table, the second arguemtn could be like {__call = function(T:{}) --" do somthing when table is called" end}

#

in the case of classes it would be __index : (T, K:string)->() | {[string]: (T)->()}

#

in the script above it the Stack table is the metatable, which as you can see in the start have Stack.__index = Stack, which is a refrence to it self. I know it could be a little hard to understand at first. ill send you all of the stuff you can do with your metatable.

#

and remenber that Class:Method(). the two dots is the same as Class.Method(Class). <-- this could be the key to understaning everything

acoustic ledge
#

A lot of help, way better than the videos and forums actually, I'll sleep on these ideas and do a bit of testing in the morning to confirm some of my knowledge but this looks promising. Mainly with the whole of idea of setmetatable() instantiating objects outlook, and Collection service (definitely looking into this one).

A lot of difficulty trying to relate the traditional class-based oop with lua oop, but I think this helped a lot, thanks so much @vivid shuttle and @bleak obsidian

vivid shuttle
#

or not its __str__

#

yea same as the repr

hot lodgeBOT
#

studio** You are now Level 4! **studio

bleak obsidian
#

try to pay it forward (actually helping people instead of just posting documentation links)

keen fable
keen fable
#

you can give operator overloads to a table, including calling the table like a function with __call

#

;compile lua lua local t=setmetatable({},{__call=function(self,x) print(x) end}) t("foo")

errant furnaceBOT
#
Program Output
foo

keen fable
#

__index is an important metamethod; while typically you see it used recursively, it's generally not the correct way to do things

#

basically, when lua goes to look for a member in a table such as myTable["foo"] it will first look inside the table. if the table does not have the member, and the table also has a metatable, it will refer to the metatable __index metamethod to look for a value. if __index is a function, it will call that function. if __index is a table, it will look for the value inside the table.
if a value is not found in the index table, and the index table has its own metatable, then lua will continue searching up the chain of index metatables until it finds a value or returns nil.
in this way you can construct inheritance and virtual functions; index is usually full of functions, so if you add a function to the front of the metatable index chain it will override any functions after it. if the function is not in the front of the metatable index chain, it will search for it on each step after returning the first one it finds.

#

;compile lua ```lua
local parentIndex={}
local parentMeta={__index=parentIndex}
function parentIndex.foo() print("parent foo") end

local childIndex={}
local childMeta={__index=childIndex}

local subIndex={}
local subMeta={__index=subIndex}

local t={}
setmetatable(t,parentMeta)
t:foo() -- parent foo

setmetatable(t,childMeta)
print(t.foo) -- nil: childmeta.__index doesn't point to the parent
setmetatable(childIndex,parentMeta) -- inherit from parent metatable
t:foo() -- parent foo
function childIndex.foo() print("child foo") end -- put override in child index
t:foo() -- finds child foo first

setmetatable(t,subMeta)
print(t.foo) -- nil again
setmetatable(subIndex,childMeta)
t:foo() -- child foo
function subIndex.foo() print("sub foo") end
t:foo() -- sub foo

t.foo=function() print("t foo") end
t:foo() -- since t has foo, this is found first; no metatable lookup```

errant furnaceBOT
#
Program Output
parent foo
nil
parent foo
child foo
nil
child foo
sub foo
t foo

keen fable
#

you can of course simplify by using the recursive metatable index, but ya that's just shorthand. the full metatable pattern is the first one

#

;compile lua ```lua
local parentIndex={}
parentIndex.__index=parentIndex
function parentIndex.foo() print("parent foo") end

local childIndex={}
childIndex.__index=childIndex

local subIndex={}
subIndex.__index=subIndex

local t={}
setmetatable(t,parentIndex)
t:foo() -- parent foo

setmetatable(t,childIndex)
print(t.foo) -- nil: childmeta.__index doesn't point to the parent
setmetatable(childIndex,parentIndex) -- inherit from parent metatable
t:foo() -- parent foo
function childIndex.foo() print("child foo") end -- put override in child index
t:foo() -- finds child foo first

setmetatable(t,subIndex)
print(t.foo) -- nil again
setmetatable(subIndex,childIndex)
t:foo() -- child foo
function subIndex.foo() print("sub foo") end
t:foo() -- sub foo

t.foo=function() print("t foo") end
t:foo() -- since t has foo, this is found first; no metatable lookup``` same result

errant furnaceBOT
#
Program Output
parent foo
nil
parent foo
child foo
nil
child foo
sub foo
t foo

vivid shuttle
keen fable
#

but they are related

vivid shuttle
#

In lua

#

You can tables

#

You have tables for everything. There’s no datastucure apart form it, or I’m I missing something?

#

For linked list, stack, queue, set, tuple, k-ary tree, binary tree etc. Idk

vivid shuttle
#

Or nvm

keen fable
#

just as a case in point

#
export type Stack<T> = typeof(setmetatable(
    {}::{
        T
    }, Stack))``` in addition to {T} being an array, the metatable you use is Stack, you don't specify typeof and you also have a type with the same name. this should be `}, Stack<T>))`
#

so you have a naming collision, incorrect type on the metatable, and the table you're using is an array when usually it should be a dict like [string]:any, or have specified fields, or do {}&T

#

long story short dont bother with types unless you're going to be serious about it

vivid shuttle
keen fable
#

really, the <T> isn't needed except for inheritance idk why you keep using it

#

idk, types is a big subject

vivid shuttle
keen fable
#

luau typesolver can't infer self

#

so all these functions need a template lua function Stack:Size():number return #self end

#
function Stack:Add(Value:any)
    table.insert(self, Value)
end``` template type missing here too, value should be T in this code
#

etc etc

#

types are annoying hence dont bother

#

casting is good enough if/where you care for it and faux types that represent what you want without the need to be typestrict

vivid shuttle
#

Yea I got you, I just use type for reading and autocompletes. And the function listed above don’t return anything, so as you said it makes it annoying to do it to every method

keen fable
#

or y'know, learn typestrict fully fingerguns

#

(not recommended)