Page 1 of 2

Help with OOP

Posted: Sat Dec 03, 2016 1:40 pm
by LeNitrous
Hello, this is related with my current project but I feel this needs a topic of its own as it would get off-topic. I'm fairly new to OOP and don't know much about it. I'm making a system that when an object is created, it adds itself to another table. Some pseudo code to get it:

Code: Select all

list = {}

item = {
	new = function(__self, id)
		self.id = id
		table.insert(list, __self.id())
	end;
	spawn = function()
		-- spawn code
	end
}
So when for example heart = item.new() is called, the object "heart" is added to the table "list" and can run functions like list["heart"]:spawn()
Is this possible? I want to make my system as flexible as I can.

Re: Help with OOP

Posted: Sat Dec 03, 2016 2:08 pm
by pgimeno
Yes it is. Note that list["heart"] is exactly equivalent to list.heart.

Also, you don't need table.insert at all. Just list[id] = instance will do.

That said, I don't understand the logic in your code above (especially self vs. __self, what function id() is, and what part is supposed to create the instance).

Re: Help with OOP

Posted: Sat Dec 03, 2016 2:10 pm
by nyenye
First of all, Lua is not a Object Oriented Programming language, but it gives you the tools to do so, and these are called metatables.

item.lua

Code: Select all

local Item = {}
local item_metatable = { __index = Item}

Item.new = function(data)
  local instance = data
  return setmetatable(instance, item_metatable)
end

Item:use = function()
  self.count = self.count - 1
end
With this you could do something like:

Code: Select all

local Item = require 'item'
local heart = Item.new({count = 3})
heat:use()
Note the difference between '.' and ':', when using ':' you pass as first paramteter, self, and when using '.', you won't get acces to self.

Later, you should organize your code, so you don't mix the creation of an item, with it being added to a list. Maybe you could mount yourself a kind of Factory which given a string would handle it all without mixing thinks. Here is some adapted code from my project given that right now I don't have time to explain any further:

factory.lua

Code: Select all

local Factory = {
    ['switch'] = {
        ['item'] = function( data )
            local instance = Item.new(data)
            return instance
        end
    }
}

function Factory.new( entity, data )
    if Factory.switch[entity] then
        return Factory.switch[entity]( data )
    end
end

return Factory
main.lua

Code: Select all

Factory = require 'factory.lua'
list = {}
table.insert(list, Factory.new('item', {id = 'heart'})
Keep in mind that this 'table.insert()', will put the new item at the end of the list, ordered numerically. If you want the key to be the id, you have to make sure that it doesn't exists on the list (otherwise will be overwritten), and do it normally:

Code: Select all

list[id] = item

Re: Help with OOP

Posted: Sat Dec 03, 2016 2:13 pm
by Sir_Silver
Yes, OOP is possible in Lua. There isn't a typical class structure in Lua like there is in Python for example, instead we can use metatables and metamethods.

Code: Select all

list = {}

item = {}
item.__index = item  --The index metamethod is defined for our item class. This means that any table whose metatable is the item class will now be able to "inherit" it's methods.

function item:new(id)
	local item_object = setmetatable({}, item)  --This creates a table and sets up the item class as the parent to our newly instanced item_object.
	
	item_object.id = id
	table.insert(list, item_object)
	
	return item_object
end

function item:spawn()
	--spawn code
end

heart = item:new(1)
I cleaned up your example a little bit, because it was just psuedo code, into an example that you can play around with if you want to.

Here are all of the metamethods if you're curious about them. You can use them to make any sort of class structure that you want, from vectors, to items and beyond!

http://lua-users.org/wiki/MetatableEvents

Re: Help with OOP

Posted: Sat Dec 03, 2016 2:16 pm
by Firehawk
I think this is what you were looking to do

Code: Select all

item_mt = {}
--This sets up item_mt as a lookup.
--__index is a metafunction which is only invoked if an index does NOT exist in the main table.
item_mt.__index = item_mt 

--We can define a function on the base.
function item_mt:base_cool_function()
    print("That was awesome!")
end

--Think of this as a sort of "item manager"
item = {
    --We'll use this table to hold registered items for later instancing.
	registered = {},
	
	new = function(id)
		assert(not item.registered[id], "This item has already been registered!")
	    
	    --Create a new table supplied with the item id and point __index to itself.
		local obj = setmetatable({id = id}, item_mt)
		obj.__index = obj
		
		item.registered[id] = obj
		return obj
	end,
	
	spawn = function(id)
		local item_class_mt = assert(item.registered[id], "This item does not exist!")
		local obj = setmetatable({}, item_class_mt)
		
		return obj
	end
}

--Register a basic item.
test_item = item.new("basic_item")

--Give it some base functionality. This works because of the use of __index.
function test_item:cool_function()
    print("Hey! That's pretty good!")
end

--We can now spawn a new item instance and call that function we defined.
local my_test_item = item.spawn("basic_item")
my_test_item:cool_function()

--We can call a function further up the chain of inheritance..
my_test_item:base_cool_function()

local their_test_item = item.spawn("basic_item")

--These are not the same instances.
print(their_test_item == my_test_item)

Re: Help with OOP

Posted: Sun Dec 04, 2016 1:27 am
by LeNitrous
I kinda gotten confused already. My goal is to use a table to contain all objects and drawing them all without calling each object individually while still being able to access their functions hence an item manager.

Code: Select all

-- items.txt
heart
circle
square
triangle
star

-- main.lua
items = {}
function love.load()
	file = "items.txt"
	if love.filesystem.isFile(file) then
		for line in love.filesystem.lines(file) do
			local object = item(line)
			table.insert(items, object)
		end
	end
end
EDIT: I can use a separate text file to append all items inside it. Now I need to know how to call a specific item. I can do it via items[1]:use() yet I need it to be like items["heart"]:use(). Any way how?

Re: Help with OOP

Posted: Sun Dec 04, 2016 4:37 am
by pgimeno
See [manual]pairs[/manual].

Re: Help with OOP

Posted: Sun Dec 04, 2016 7:08 am
by Sir_Silver
Firehawks example sounds exactly like what you wanted, it's basically an item manager. Though it probably uses concepts like metatables that you are unfamiliar with. Learning metatables is probably where you want to start if you want to look into the use of objects.

Re: Help with OOP

Posted: Sun Dec 04, 2016 7:30 am
by ivan
Just a small note:

Code: Select all

item = {}
item.__index = item

function item:new(id)
	local item_object = setmetatable({}, item)
	[...]
	return item_object
end
Seems that "item.__index = item" doesn't do anything at all.
I believe you mean:

Code: Select all

item = {} -- interface
itemMT = { __index = item } -- metatable (falls back to the interface)

function item:new(id)
	local item_instance = setmetatable({}, itemMT) -- this is where the magic happens
	[...]
	return item_instance 
end
It's clearer to separate the "interface" from the "metatable",
especially when inheritance is involved.

Re: Help with OOP

Posted: Sun Dec 04, 2016 7:41 am
by nyenye
LeNitrous wrote: EDIT: I can use a separate text file to append all items inside it. Now I need to know how to call a specific item. I can do it via items[1]:use() yet I need it to be like items["heart"]:use(). Any way how?
Given your code, you only need to change 2 lines to do what you want:

Code: Select all

-- main.lua
items = {}
function love.load()
   file = "items.txt"
   if love.filesystem.isFile(file) then
      for line in love.filesystem.lines(file) do
         local object = item(line) -- object is the ID for the item
         items[object] = object -- this will put on the table, a key 'heart', and a value 'heart'
      end
   end
end
Having said that I recommend that you read this wiki: lua-users.org/wiki/TablesTutorial (maybe the very last section is what you are looking for... dunno). Also keep in mind that items.heart, is a string!!, so you won't be able to get any OOP functionalities (like calling methods, or adding properties like counters or whatever). Maybe do:

Code: Select all

items[object] = { count = 5 }
-- so you can do 
if items.heart.counter > 0 then
    ---use
end
And if you make yourself an Item class (see earlier responses) you can access methods right away.

EDIT: Just to clarify something about the metatable, you can think of it as a second list of properties for a table. So if you call some property that is not known to the 'primary' table, then it will look up on the metatable (or 'secondary' table). This is mostly useful for methods, which shouldn't have to change depending on the instance. (See earlier responses to see examples)