Simple Objects

I have been trying to work on a very simple object system for a game. I would like to create a new class for a Player/Enemy/Controls however I am having problems understanding metatables and groups. For example
 
Secondly, if I use
 
As a basic example:
 

local Controls = {} local ControlsMeta = {\_\_index = Controls} function Controls:newJoystick(options) local joystick = display.newGroup() -- container joystick.border = display.newCircle(.....) -- thumb control joystick.thumb = display.newCircle(.....) joystick.onMove = function(event) print(self) end joystick.thumb:addEventListener("touch",joystick.onMove) -- Insert parts joystick:insert(joystick.border) joystick:insert(joystick.thumb) -- setmetatable(joystick,self) -- Returns error if I uncomment this return joystick end return Controls

 
 
When I run the code and hover over the control, Corona throws an error:
 

Corona Runtime Error attempt to call a nil value Do you want to relaunch the project?

 
Why can’t I insert the items into the joystick group?

Well, first thing I would do is to encapsulate your OOP in a base class, which allows the creation of new classes/prototypes and nothing else, and then derive everything from that ; I tend to use :

G.Base = \_G.Base or { new = function(s,...) local o = { } setmetatable(o,s) s.\_\_index = s o:initialise(...) return o end, initialise = function() end }

Metatables are kind of like ‘behaviour modifier’ for tables. So when you do .__index on the metatable, it is like saying, for the table that uses this, look here for unknown table members - the net effect of this is it looks in the metatable for the unknown element, allowing you to create a chain.

The above code breaks down as 

local o = {} create a new table - this will either be a new class or an instance - in reality they are all prototypes 

setmetatable(o,s) use ‘s’ as this objects metatable, this is the parent prototype.

s.__index = s when you can’t find something look for it in the metatable.

o:initialise(…) call the constructor

then there’s just a dummy constructor.

The answer to your question, I think, is that you have your event listener as a function listener, you should make it a table listener, so it calls joystick.touch with two parameters self and e (or call joystick:touch with one parameter, same difference).

Generally when writing OOP code (not always) you should be using : for methods and . for members.

Also, it is not a good idea to create mixin classes out of Corona objects unless you really have to. This may involve writing some adapter code - a move() method rather than assigning x and y, but it does save a lot of issues, because you cannot guarantee what Corona will do with their objects. The most obvious problem is when you try to set the metatable of joystick (itself a displayGroup), I’m pretty sure Corona have already done this as it is the descendant of some other object. When you do mixin classes you really have to decorate.

I wrote something similar https://github.com/autismuk/Executive/blob/master/utils/controller.lua ; this is for my own object system which has its own constructors and destructors but it should help you get the general idea :slight_smile: Corona does not normally have automatic destructors. Aside from the constructor() destructor() stuff (and the self:name() call) it is pretty much normal lua.

Hi Paul,

Thanks for the thorough response. I have been reading a few more articles and have since swapped the listener to read:

joystick.thumb:addEventListener("touch",Controls)

and then wrote my method

function Controls:touch(event) ...

This works fine as the event has a reference to the “thumb” and hence I can use event.target.parent to access my original “joystick” group as defined in Controls:newJoystick().

I’ll have a go with the code you provided (many thanks), but for my own curiosity, how would you create a class without the wrapper? For the sake of keeping lines of code down, how could I “fix” my original code so that I can have a few display objects, properties and also methods.

@FortyFourDigital,

There are lots of wonderful Lua OOP modules and wrappers out there, but I find this one very simple and functional at the  same time:

https://github.com/bjorn/lua-oop/blob/master/object.lua

Thanks for this roaminggamer - I guess it’s similar to Paul’s wrapper.

I’ve done a quick test and still can’t access a method:

main.lua:

\_G.Object = require("object") \_G.Controls = require("controls") local joystick = Controls:new({name="myStick"}) joystick:testMethod() -- This gives "attempt to call method 'testMethod' (a nil value)

My controls.lua

local Controls = Object({ someProperty = 0 }) function Controls:new(options)    local joystick = display.newGroup()    -- deleted extraneous code joystick.thumb = display.newCircle(0,0,30) joystick.thumb:addEventListener("touch",Controls) end function Controls:touch(event)    -- touch events work and update the instance properties correctly end function Controls:testMethod()    print("Hello") -- Trying to call this end return Controls

Perhaps I missed something obvious?

I wrote a Lua class implementation  tutorial that is posted at http://professorcook.org/lua/ClassDesign.html

to continue your discussion, i am trying to implement a Cartesian graph class that would be a sub-class of a display group.

the following code shows that it is possible.

g=display.newGroup()
g.x=display.contentCenterX
g.y=display.contentCenterY
display.newRect(g,0,0,50,50)
g.apple=22
print(g.apple)
function g:test()
  print(55, self.x, self.y)
end
g:test() --prove props/methods can be added to system object
for a,b in ipairs(g) do print(a,b) end

OUTPUT

22

55    360    640

a problem arises in implementing a copy constructor.

i know that the docs say that ipairs doesn’t work on system objects.

i think that is a bug that prevents sub-classing groups, which is very desirable.

Can the behavior be modified to only iterate over user-defined fields?

Hi, my micro version is at the top of a file - it defines a class ‘Base’ which has two methods, new(), which creates subclasses or instances, and a constructor initialise(). The Executive thing is a very different approach, but feel free to nick any of the code :slight_smile:

There are plenty of good tutorials and libraries, just find one which suits your coding style

I think the problem lies in the metatable fourty four. Try this

– joystick.lua

-- base table local Controls = {} -- metatable which is associated with table which cruicially calls the testmethod function local Controls\_mt = {\_\_index = Controls} -- private function, no need to be accesed by other modules local function onTouch(event)     print( "touched" ) end -- constructor, i prefer using the dot operator but its up to you function Controls:new(options)    local joystick = display.newGroup()    -- deleted extraneous code    joystick.thumb = display.newCircle(0,0,30)    joystick.thumb:addEventListener("touch",onTouch)    return setmetatable(joystick,Controls\_mt) end -- method instance function Controls:testMethod()    print("Hello") end -- you know what this does return Controls

and

– main.lua

  local require = require local control = require("joystick") local joystick = control:new({}) joystick:testMethod() -- prints hello  

Hi @86lemonade68

I had tried to use your code before (I’ve re-written my controller class several times!). I can call the test method now, but as soon as I hover my mouse over the joystick control in the simulator - it crashes and says:

“Corona Runtime Error - attempt to call a nil value Do you want to relaunch the project”

What is causing this? I am doing nothing else aside from moving the mouse over the circle.

EDIT:

if I change my controller class to use joystick = {} rather than joystick = display.newGroup() then I don’t get an error. I have to use table.insert(joystick,joystick.border) and table.insert(joystick,joystick.thumb) to add display objects into the joystick table.

The only problem now is that if I set an event listener on joystick.thumb, I cannot use event.target.parent in the onTouch function.

@paulscottrobson

I created a simple test control class using your code, and it still gives me the same issue. I cannot call any of my own methods on the joystick object “Corona - attempt to call method ‘someMethod’ (a nil value)”

The controls show up fine but I’m obviously missing something important.

Difficult to say because I don’t know how you are using it - the module works, but without seeing your version of it I can’t tell you

Hi Paul, This is my demo code

main.lua

display.setStatusBar(display.HiddenStatusBar) \_G.screenWidth = display.contentWidth - (display.screenOriginX\*2) \_G.screenHeight = display.contentHeight - (display.screenOriginY\*2) \_G.screenTop = 0 + display.screenOriginY \_G.screenRight = display.contentWidth - display.screenOriginX \_G.screenBottom = display.contentHeight - display.screenOriginY \_G.screenLeft = 0 + display.screenOriginX \_G.screenCenterX = display.contentWidth/2 \_G.screenCenterY = display.contentHeight/2 local Controls = require("controls") local joystickMove, joystickShoot local player = display.newGroup() local head = display.newRect(0,-20,10,10) local body = display.newRect(0,0,50,50) head:setFillColor(1,0,0) body:setFillColor(0,1,0) player:insert(body) player:insert(head) player.x = screenCenterX player.y = screenCenterY joystickMove = Controls:newJoystick({name="movement",x=screenLeft+60, y=screenBottom-60}) joystickShoot = Controls:newJoystick({name="shoot",x=screenRight-60,y=screenBottom-60}) local function doStuff(event) if(joystickMove.isActive) then print("Angle",joystickMove:getAngle()) -- THIS THROWS THE ERROR end end Runtime:addEventListener("enterFrame",doStuff)

And this is my controls.lua

\_G.Base =  \_G.Base or { new = function(s,...) local o = { } setmetatable(o,s) s.\_\_index = s o:initialise(...) return o end, initialise = function() end } local Controls = Base:new() function Controls:initialise(options) -- I have tried to move my newJoystick code into here - same problem end function Controls:newJoystick(options) local joystick = display.newGroup() -- Properties joystick.name = options.name joystick.maxDist = 50 joystick.snapBackSpeed = 0.7 joystick.x = options.x joystick.y = options.y -- Create the container joystick.border = display.newCircle(0,0,48) joystick.border:setFillColor  (0.6,0.6,1.0,0.18) joystick.border:setStrokeColor(0.6,0.6,1.0,1) -- Create the thumb control joystick.thumb = display.newCircle(0,0,30) joystick.thumb:setFillColor  (0.6,0.6,1.0,0.38) joystick.thumb:setStrokeColor(0.6,0.6,1.0,1) joystick.thumb.x0 = 0 joystick.thumb.y0 = 0 -- Event listeners joystick.thumb:addEventListener("touch",Controls) -- Insert parts joystick:insert(joystick.border) joystick:insert(joystick.thumb) return joystick end function Controls:touch(event) local phase = event.phase local target = event.target local parent = target.parent local ex = event.x - target.x0 local ey = event.y - target.y0 if(phase=="began") then display.getCurrentStage():setFocus(target,event.id) target.isFocus = true target.x0 = ex - target.x target.y0 = ey - target.y parent.isActive = true transition.cancel(target) elseif target.isFocus then if(phase == "moved") then parent.distance = math.sqrt (ex\*ex + ey\*ey) if parent.distance \> parent.maxDist then  parent.distance = parent.maxDist end parent.angle = ((math.atan2( ex-0,ey-0 )\*180 / math.pi) - 180 ) \* -1 parent.percent = parent.distance / parent.maxDist target.x = math.cos( math.rad(parent.angle-90) ) \* (parent.maxDist \* parent.percent)  target.y = math.sin( math.rad(parent.angle-90) ) \* (parent.maxDist \* parent.percent) elseif(phase=="ended" or phase=="cancelled") then target.x0 = 0 target.y0 = 0 target.isFocus = false display.getCurrentStage():setFocus(nil,event.id) parent.isActive = false transition.to(target,{ delay = 0, time = 200, x = 0, y = 0 }) end end end function Controls:getAngle() print("Calling getAngle") end return Controls

What have I missed?

lua OOP doesn’t work like that - it works a bit like Python’s, using the self keyword.

I’ve rewritten it at https://github.com/autismuk/Executive/blob/master/utils/d44.lua - you probably need to read through it in conjunction with this. http://www.lua.org/pil/16.html 

Fundamentally, don’t tinker with Corona objects - treat them as things you manipulate in an object. 

Well, first thing I would do is to encapsulate your OOP in a base class, which allows the creation of new classes/prototypes and nothing else, and then derive everything from that ; I tend to use :

G.Base = \_G.Base or { new = function(s,...) local o = { } setmetatable(o,s) s.\_\_index = s o:initialise(...) return o end, initialise = function() end }

Metatables are kind of like ‘behaviour modifier’ for tables. So when you do .__index on the metatable, it is like saying, for the table that uses this, look here for unknown table members - the net effect of this is it looks in the metatable for the unknown element, allowing you to create a chain.

The above code breaks down as 

local o = {} create a new table - this will either be a new class or an instance - in reality they are all prototypes 

setmetatable(o,s) use ‘s’ as this objects metatable, this is the parent prototype.

s.__index = s when you can’t find something look for it in the metatable.

o:initialise(…) call the constructor

then there’s just a dummy constructor.

The answer to your question, I think, is that you have your event listener as a function listener, you should make it a table listener, so it calls joystick.touch with two parameters self and e (or call joystick:touch with one parameter, same difference).

Generally when writing OOP code (not always) you should be using : for methods and . for members.

Also, it is not a good idea to create mixin classes out of Corona objects unless you really have to. This may involve writing some adapter code - a move() method rather than assigning x and y, but it does save a lot of issues, because you cannot guarantee what Corona will do with their objects. The most obvious problem is when you try to set the metatable of joystick (itself a displayGroup), I’m pretty sure Corona have already done this as it is the descendant of some other object. When you do mixin classes you really have to decorate.

I wrote something similar https://github.com/autismuk/Executive/blob/master/utils/controller.lua ; this is for my own object system which has its own constructors and destructors but it should help you get the general idea :slight_smile: Corona does not normally have automatic destructors. Aside from the constructor() destructor() stuff (and the self:name() call) it is pretty much normal lua.

Hi Paul,

Thanks for the thorough response. I have been reading a few more articles and have since swapped the listener to read:

joystick.thumb:addEventListener("touch",Controls)

and then wrote my method

function Controls:touch(event) ...

This works fine as the event has a reference to the “thumb” and hence I can use event.target.parent to access my original “joystick” group as defined in Controls:newJoystick().

I’ll have a go with the code you provided (many thanks), but for my own curiosity, how would you create a class without the wrapper? For the sake of keeping lines of code down, how could I “fix” my original code so that I can have a few display objects, properties and also methods.

@FortyFourDigital,

There are lots of wonderful Lua OOP modules and wrappers out there, but I find this one very simple and functional at the  same time:

https://github.com/bjorn/lua-oop/blob/master/object.lua

Thanks for this roaminggamer - I guess it’s similar to Paul’s wrapper.

I’ve done a quick test and still can’t access a method:

main.lua:

\_G.Object = require("object") \_G.Controls = require("controls") local joystick = Controls:new({name="myStick"}) joystick:testMethod() -- This gives "attempt to call method 'testMethod' (a nil value)

My controls.lua

local Controls = Object({ someProperty = 0 }) function Controls:new(options)    local joystick = display.newGroup()    -- deleted extraneous code joystick.thumb = display.newCircle(0,0,30) joystick.thumb:addEventListener("touch",Controls) end function Controls:touch(event)    -- touch events work and update the instance properties correctly end function Controls:testMethod()    print("Hello") -- Trying to call this end return Controls

Perhaps I missed something obvious?

I wrote a Lua class implementation  tutorial that is posted at http://professorcook.org/lua/ClassDesign.html

to continue your discussion, i am trying to implement a Cartesian graph class that would be a sub-class of a display group.

the following code shows that it is possible.

g=display.newGroup()
g.x=display.contentCenterX
g.y=display.contentCenterY
display.newRect(g,0,0,50,50)
g.apple=22
print(g.apple)
function g:test()
  print(55, self.x, self.y)
end
g:test() --prove props/methods can be added to system object
for a,b in ipairs(g) do print(a,b) end

OUTPUT

22

55    360    640

a problem arises in implementing a copy constructor.

i know that the docs say that ipairs doesn’t work on system objects.

i think that is a bug that prevents sub-classing groups, which is very desirable.

Can the behavior be modified to only iterate over user-defined fields?

Hi, my micro version is at the top of a file - it defines a class ‘Base’ which has two methods, new(), which creates subclasses or instances, and a constructor initialise(). The Executive thing is a very different approach, but feel free to nick any of the code :slight_smile:

There are plenty of good tutorials and libraries, just find one which suits your coding style

I think the problem lies in the metatable fourty four. Try this

– joystick.lua

-- base table local Controls = {} -- metatable which is associated with table which cruicially calls the testmethod function local Controls\_mt = {\_\_index = Controls} -- private function, no need to be accesed by other modules local function onTouch(event)     print( "touched" ) end -- constructor, i prefer using the dot operator but its up to you function Controls:new(options)    local joystick = display.newGroup()    -- deleted extraneous code    joystick.thumb = display.newCircle(0,0,30)    joystick.thumb:addEventListener("touch",onTouch)    return setmetatable(joystick,Controls\_mt) end -- method instance function Controls:testMethod()    print("Hello") end -- you know what this does return Controls

and

– main.lua

  local require = require local control = require("joystick") local joystick = control:new({}) joystick:testMethod() -- prints hello