Using Composer with OOP

Hi,

I have three Composer scenes in my game and I want to display the same menu buttons in all three. Therefore, I have created a separate module called menu.lua. The calls to menu.lua are the same in all three scenes:

local menu = require("menu.lua") function scene:create(event) ... local menuGroup = menu:create() sceneGroup:insert(menuGroup) ... end function scene:show(event) local phase = event.phase if (phase == "will") menu:setMenuVisible(true) elseif (phase == "did") then ... end ... end function scene:hide(event) local phase = event.phase if (phase == "will") menu:setMenuVisible(false) elseif (phase == "did") then ... end end

The code in menu.lua is:

local M = {} function M:create() -- Set up self.soundButtonDisplayGroup = display.newGroup() self.menuGroup = display.newGroup() -- Set sound volume control button self.soundButton = display.newImageRect("soundbutton.png", 50, 50) self.soundButton.x = 100 self.soundButton.y = 100 self.soundButtonDisplayGroup:insert(self.soundButton) self.soundButton.touch = onSoundButtonTouch self.soundButton:addEventListener("touch", self.soundButton) self.menuGroup:insert(self.soundButtonDisplayGroup) ... return self.menuGroup end function M:setMenuVisible(visible) if (visible) then self.menuGroup.isVisible = true else self.menuGroup.isVisible = false end end function M.onSoundButtonTouch(self, event)     -- Functionality for toggling music on and off          return true end return M

The problem with this is that the menu disappears when going from one scene to another and then back. If I omit setMenuVisible() then the menu from one scene will be visible in another, below the new one.

What I want to achieve is to have three completely “independent” menu instances of the menu “class” that are displayed and used with no “relations” to the menus in the other scenes. How can I do this?

If your menu is the same they why do you need 3 separate instances of it?  Just create it once at the parent level (globally if you will) and show and hide it on demand.

  1. you shouldn’t hide until the did phase

  2. your “class” module has problems, as do your references to “instances” in your scene

you return the “self.menuGroup” from create, not “self”, and you didn’t set the instance’s metatable anyway, so each “instance” doesn’t have its own setMenuVisible, only the class does – that’s probably why you ended up calling the setMenuVisible method on the class instead of the instance.

iow, your “class” is (apparently) set up to work as a singleton, but you’re trying to create/use multiple instances – won’t work as expected.

you’d need to fix the class, then fix the instance calls.

Thanks for you anwers, guys!

@Sphere Game Studios: you are right, I actually only need to create the menu once since it is identical in all scenes. I have changed the code as you can see below, but the menu only appears in the scene where it is created but not in the others. You said that I should create the menu “at the parent level”, is this not how to do it (if I want to avoid global variables)?

local menu = require("menu.lua") function scene:create(event) ... local menuGroup = menu.create() sceneGroup:insert(menuGroup) ... end function scene:show(event) local phase = event.phase if (phase == "will") menu.setMenuVisible(true) elseif (phase == "did") then ... end ... end function scene:hide(event) local phase = event.phase if (phase == "will") ... elseif (phase == "did") then menu.setMenuVisible(false) end end

local M = {} local menuGroup = display.newGroup() function M.create() -- Set up local soundButtonDisplayGroup = display.newGroup() -- Set sound volume control button local soundButton = display.newImageRect("soundbutton.png", 50, 50) soundButton.x = 100 soundButton.y = 100 soundButtonDisplayGroup:insert(soundButton) soundButton.touch = onSoundButtonTouch soundButton:addEventListener("touch", soundButton) menuGroup:insert(soundButtonDisplayGroup) ... return menuGroup end function M.setMenuVisible(visible) if (visible) then menuGroup.isVisible = true else menuGroup.isVisible = false end end function onSoundButtonTouch(self, event) -- Functionality for toggling music on and off return true end return M

Just require it in main.lua and make it global.  Then you can reference it from any scene in your project.

Others may disagree but there is nothing “wrong” with scoping (some) things like this globally.

you’ll likely also need to rethink the show/hide logic – you won’t want your “previous” scene hiding the one-and-only-menu-instance that your “current” scene just tried to show… right?

(the logic as you have it now would be more appropriate for the each-scene-has-its-own-menu-instance way of structuring things)

perhaps just show it once when entering these three scenes, and hide it once if/when leaving them.

also consider that if you continue to insert it into the scene.view group (as opposed to just floating it over the top of composer’s stage) then you could get some weird visual artifacts during transitions as you re-insert the menu from previous to current scene’s view.

I’d really like to avoid globals if possible and I need to make sure that there is no other way of doing this before resorting to that solution…

I tried to create the menu (once) with a call from main.lua. Since floating the menu on top of the composer stages is not an option (other display objects need to be able to go on top of the menu) I then inserted the menuGroup into the sceneGroup on all three views in their scene:create() functions. That did not work either since the menu didn’t display in all scenes, even if I never explicitly made it invisible in scene:hide(). If I do not add the menu in the sceneGroup of any of the three scenes, it works, but as I said, that is not an option.

@davebollinger: looking at the code in my original post, what exactly would I have to change to not make it behave like a singleton? Would you perhaps have some code examples? I don’t want you to code my app for me, just please show me how your proposed changes would be done.

Creating display objects in a separate module and then using that module to create and display those objects in different Composer scenes (“independently”) just seems like a really basic task. I’ve googled this but apart from sharing primitives (numbers, strings) and booleans etc, I can’t find any relevant info on how to do the same with display objects. Am I approaching this whole menu-thing completely wrong? What would be the textbook way of doing this?

It will need a metatable - https://docs.coronalabs.com/api/library/global/setmetatable.html

very generally, cuz i’m lazy, one possible pattern goes like this:

-- Menu.lua local Menu = {} local Menu\_mt = { \_\_index = Menu } -- note that this is just a "dot'ed" function -- we don't strictly need "self" (which would refer to the class Menu, which we already have access to in this scope anyway) -- (this is akin to a "static method" in other language's jargon) function Menu.new(parentGroup, whatever\_other\_params\_you\_need) local instance = setmetatable({}, Menu\_mt) -- instance now picks up all of Menu's methods instance.group = display.newGroup() parentGroup:insert(instance.group) return instance end -- note that this is a "colon'ed" function -- we DO need "self" (which refers to the particular instance) function Menu:destroy() display.remove(self.group) self.group = nil end -- note that this is a "colon'ed" function -- we DO need "self" (which refers to the particular instance) function Menu:setVisible(value) self.group.isVisible = value end return Menu

and

-- scene.lua -- (lots of non-relevant details left out) local Menu = require("Menu") -- this is the class local menu = nil -- this WILL BE the instance, for THIS scene alone -- in create menu = Menu.new(scene.view) -- in show menu:setVisible(true) -- in hide menu:setVisible(false) -- in destroy menu:destroy() menu = nil

fwiw, at this point the whole notion of menu:setVisible() become superflous anyway if you’re only doing it in response to scene.show/hide

you’re showing/hiding the entire scene group, so no need to explicitly/separately show/hide the menu within it

Hi,

I have not implemented this yet, but my plan is to create a “menu” module in main.lua and attach it to the Composer object using composer.setVariable, and retrieve it when needed using composer.getVariable.

If you go this route, and it works, let me know.  :wink:

-dev

I’m late to the conversation. If you want the menu always visible, simply don’t put it in a scene view group. Display objects that are not in a composer group are drawn at the highest level of OpenGL. native.* will be on top of that. We call this HUD mode. It’s used frequently by apps implementing a tabBar menu at the bottom of the page.

You can have a method in your menu module that you could have scenes call when they show that changes the content of the menu. This way the menu is always on the screen and isn’t effected by scene transitions 

I think @develepant is heading in that direction in his post above.

-- menu.lua local M = {} function M.new() -- create your display objects function M.update() -- change the values of the display objects based on the scene -- you will need to figure out the parameters needed to pass here.

In main.lua:

local menu = require("menu") local myMenu = menu.new(params)

in random scene:

local menu = require("menu") ... function scene:show( event )      if event.phase == "did" then           menu.update(params)     end end

or something like that.

Of course I could be completely off base on what you’re trying to do.

Rob

First of all, thanks for all your input!

I’ decided to go on davebollinger’s template since I wanted to learn anyway how to create and instantiate independent “classes” in lua (given my previous experiences with the Java language).

@Develephant: that was actually my next idea if I couldn’t get the “OOP-approach” working. If I understand you correctly, your approach would be to create the menu once and then just reuse it by passing the menu object (or display group) from scene to scene. That would practically be the same approach as taken by Sphere Game Studios, but without the use of globals, right?

@Rob: no, you are spot on what I wanted to acheive but I have to add the menu to Composer’s scene group since other objects need to be able to move in front of the menu.

Now that I have it all working, I realize that the main thing that messed it up for me was how/when to use “self” and the whole dot/colon annotation, especially when used from within the instantiated class calling its own functions (like a listener). I will read up on this since it is essential to understand, I think.

Again, thanks for all your help!

If your menu is the same they why do you need 3 separate instances of it?  Just create it once at the parent level (globally if you will) and show and hide it on demand.

  1. you shouldn’t hide until the did phase

  2. your “class” module has problems, as do your references to “instances” in your scene

you return the “self.menuGroup” from create, not “self”, and you didn’t set the instance’s metatable anyway, so each “instance” doesn’t have its own setMenuVisible, only the class does – that’s probably why you ended up calling the setMenuVisible method on the class instead of the instance.

iow, your “class” is (apparently) set up to work as a singleton, but you’re trying to create/use multiple instances – won’t work as expected.

you’d need to fix the class, then fix the instance calls.

Thanks for you anwers, guys!

@Sphere Game Studios: you are right, I actually only need to create the menu once since it is identical in all scenes. I have changed the code as you can see below, but the menu only appears in the scene where it is created but not in the others. You said that I should create the menu “at the parent level”, is this not how to do it (if I want to avoid global variables)?

local menu = require("menu.lua") function scene:create(event) ... local menuGroup = menu.create() sceneGroup:insert(menuGroup) ... end function scene:show(event) local phase = event.phase if (phase == "will") menu.setMenuVisible(true) elseif (phase == "did") then ... end ... end function scene:hide(event) local phase = event.phase if (phase == "will") ... elseif (phase == "did") then menu.setMenuVisible(false) end end

local M = {} local menuGroup = display.newGroup() function M.create() -- Set up local soundButtonDisplayGroup = display.newGroup() -- Set sound volume control button local soundButton = display.newImageRect("soundbutton.png", 50, 50) soundButton.x = 100 soundButton.y = 100 soundButtonDisplayGroup:insert(soundButton) soundButton.touch = onSoundButtonTouch soundButton:addEventListener("touch", soundButton) menuGroup:insert(soundButtonDisplayGroup) ... return menuGroup end function M.setMenuVisible(visible) if (visible) then menuGroup.isVisible = true else menuGroup.isVisible = false end end function onSoundButtonTouch(self, event) -- Functionality for toggling music on and off return true end return M

Just require it in main.lua and make it global.  Then you can reference it from any scene in your project.

Others may disagree but there is nothing “wrong” with scoping (some) things like this globally.

you’ll likely also need to rethink the show/hide logic – you won’t want your “previous” scene hiding the one-and-only-menu-instance that your “current” scene just tried to show… right?

(the logic as you have it now would be more appropriate for the each-scene-has-its-own-menu-instance way of structuring things)

perhaps just show it once when entering these three scenes, and hide it once if/when leaving them.

also consider that if you continue to insert it into the scene.view group (as opposed to just floating it over the top of composer’s stage) then you could get some weird visual artifacts during transitions as you re-insert the menu from previous to current scene’s view.

I’d really like to avoid globals if possible and I need to make sure that there is no other way of doing this before resorting to that solution…

I tried to create the menu (once) with a call from main.lua. Since floating the menu on top of the composer stages is not an option (other display objects need to be able to go on top of the menu) I then inserted the menuGroup into the sceneGroup on all three views in their scene:create() functions. That did not work either since the menu didn’t display in all scenes, even if I never explicitly made it invisible in scene:hide(). If I do not add the menu in the sceneGroup of any of the three scenes, it works, but as I said, that is not an option.

@davebollinger: looking at the code in my original post, what exactly would I have to change to not make it behave like a singleton? Would you perhaps have some code examples? I don’t want you to code my app for me, just please show me how your proposed changes would be done.