Returning the result of an event listener outside a custom class

I’m fairly new to corona and I’m trying to wrap my head around how to use classes and event listeners together. I’ve read some things on the topic but I’m not quite getting it.

 

I’m making something like a board game, where the user can choose a rune by tapping on it, and then tap again to place the rune. This means there are two types of runes in my game, those that are playable and those that have already been played.

 

I’ve made class to for the runes and there data. It looks like this:

local rune = {} local rune\_mt = {\_index = rune} -- set up image sheet local runeInfo = require("runesSheet") local runeSheet = graphics.newImageSheet( "runes.png", runeInfo:getSheet() ) function rune.new(value, xPos, yPos, isSelectable) -- constuctor local newRune = display.newImageRect(runeSheet, value, 52, 52) newRune.x = xPos newRune.y = yPos newRune.value = value -- this is used both to determine which sprite shows up, but also the value of the rune for comparisson and game logic newRune.isSelectable = isSelectable return setmetatable( newRune, rune\_mt ) end return rune

Now before I start using classes when I just had everything in one messy file, I had an event listeners set up on the playable runes so that when the user tapped on them, they scaled and there value was saved, so that I would know what rune had been chosen. So rune1 was just a imageRect declared locally in my main.lua file. It looked like this and worked:

local currentChoice local function runeTapped(event) local thisRune = event.target if (currentChoice) then transition.scaleTo( currentChoice, {xScale = 1, yScale = 1, time = 100} ) end transition.scaleTo( thisRune, {xScale = 1.2, yScale = 1.2, time = 100} ) currentChoice = thisRune return true end rune1:addEventListener( "tap", runeTapped )

But now that rune1 isn’t just a local variable, but a class,  I get the error that addEventLister has a nil value. Okay, that makes sense, I haven’t declared addEventListener inside of rune.lua

 

What I can’t seem to grasp is this. I want the runeTapped event to happen in my main.lua file, because I need to be able to effect the currentChoice variable so that I know if there is an active selection by the player and what that is, for the next tap, which could be on an object of a different class. 

 

My first thought was to add something like this to my rune class:

if (self.isPlayable) then self:addEventListener( "tap", listener ) end

but if I do that, how do I write the listener function so that it passes a value back into main.lua? Or am I going about this the wrong way?

It’s a bit hard to tell if you are doing this already from the code you have pasted, but when rune.new() is called it should be assigned to an object. E.g.

–in main.lua

local myNewRune = rune.new(200, 50, 50, true)

That way you have a reference to the object created by the new() function, which is what the isSelectable property was assigned to.

So in main.lua you would now be able to do this:

if (myRune.isPlayable) then myRune:addEventListener( "tap", listener ) end

Alternatively you could add the listener in your rune class, and then have a global “setter” function on main.lua which is called from that touch listener:

--main.lua local valueIWantToSetInMainDotLua = 0 function setMyValue(val) valueIWantToSetInMainDotLua = val end ---------------------------------------------------------------- --rune.lua local function listener(event) if event.phase == "ended" then setMyValue(event.target.value) end end rune:addEventListener("touch", listener)

The important thing there is that the function in main.lua does not have the word “local” in front of it, so it can be called anywhere. Generally not a great way of doing things, but handy if you have a function that needs to be called in lots of places.

Ah yes, you’re right, I did forget to include that part of the code. Yes in my main.lua file I have:

local rune1 = runes.new(1, display.contentCenterX - 100, display.contentCenterY + 100, true)

But even with that variable defined early on as soon as I launch the simulator I get the error

Attempt to call method ‘addEventListener’ (a nil value)

and it references this line

rune1:addEventListener( "tap", listener )

It doesn’t really give me much else on the error (at least not in the places I know to look, but I assume it’s a nil value because in runes.lua I haven’t defined addEventListener.

The issue may be I’m not sure how to overload a method in lua. 

I did try your second suggestion with the global function in main.lua. First I changed rune.new to look like this:

function rune.new(value, xPos, yPos, isPlayable) -- constuctor local newRune = display.newImageRect(runeSheet, value, 52, 52) newRune.x = xPos newRune.y = yPos newRune.value = value -- this is used both to determine which sprite shows up, but also the value of the rune for comparisson and game logic newRune.isPlayable = isPlayable if isPlayable then newRune:addEventListener( "tap", returnChoice ) end return setmetatable( newRune, rune\_mt ) end 

but when I tried that, the project ran, but as soon as I tap on rune1 I get this error:

Attempt to call a nil value

I then just ignored the isPlayable part, and tried several different ways to add the event listener inside runes.lua right after the runes.new() function.

local runes = require("runes") local rune1 = runes.new(1, display.contentCenterX - 100, display.contentCenterY + 100) local currentChoice function setChoice(choice) if (currentChoice) then transition.scaleTo( currentChoice, {xScale = 1, yScale = 1, time = 100} ) end transition.scaleTo( thisRune, {xScale = 1.2, yScale = 1.2, time = 100} ) currentChoice = choice end

and my new runes.lua looked like this:

local rune = {} local rune\_mt = {\_index = rune} -- needed variables local runeInfo = require("runesSheet") local runeSheet = graphics.newImageSheet( "runes.png", runeInfo:getSheet() ) -- private functions local function returnChoice( event) setChoice(event.target) end -- public funtions function rune.new(value, xPos, yPos) -- constuctor local newRune = display.newImageRect(runeSheet, value, 52, 52) newRune.x = xPos newRune.y = yPos newRune.value = value -- this is used both to determine which sprite shows up, but also the value of the rune for comparisson and game logic return setmetatable( newRune, rune\_mt ) end self:addEventListener( "tap", returnChoice ) return rune

I got the error:

Attempt to index global ‘self’ (a nil value)

 

File: runes.lua

Line: 30

 

stack traceback:

runes.lua:30: in main chunk

[C]: in function ‘require’

?: in function <?:761>

(tail call): ?

main.lua:37: in main chunk

 

So line 37 is : local runes = require(“runes”)

and line 30 was where i was trying to add the event listener. Instead of self:addEventListener I also tried rune:addEventListener which gave me mostly the same error although this time it began with 

Attempt to call method ‘addEventListener’ (a nil value)

and then the rest was the same.

I also tried skipping the call to returnChoice, and just calling the global function setChoice but got the same errors.

Looking at https://docs.coronalabs.com/api/event/tap/index.html I’m guessing the problem has to do with the fact that although my customer class starts as a display object, it now has other stuff added on to it which seems fine if it’s all in main.lua but it doesn’t like being broken up. So how do I get to just the display object part of it?

So I figured it out eventually. It ended up being fairly simple. In my rune.new constructor I needed to set my newRune = {} and then create a newRune.img which was equal to my display item. So like this:

-- -- rune.lua -- local rune = {} local rune\_mt = {\_index = rune} -- needed variables local runeInfo = require("runesSheet") local runeSheet = graphics.newImageSheet( "runes.png", runeInfo:getSheet() ) -- private functions -- public funtions function rune.new(value, xPos, yPos, isPlayable) -- constuctor newRune = {} newRune.img = display.newImageRect(runeSheet, value, 52, 52) newRune.img.x = xPos newRune.img.y = yPos newRune.value = value -- this is used both to determine which sprite shows up, but also the value of the rune for comparisson and game logic newRune.isPlayable = isPlayable return setmetatable( newRune, rune\_mt ) end return rune

Then in my main.lua file I did this:

local runes = require("runes") local currentChoice local rune1 = runes.new(1, display.contentCenterX - 100, display.contentCenterY + 100, true) function runeTapped(event) local thisRune = event.target if (currentChoice) then transition.scaleTo( currentChoice, {xScale = 1, yScale = 1, time = 100} ) end transition.scaleTo( thisRune, {xScale = 1.2, yScale = 1.2, time = 100} ) currentChoice = thisRune return true end rune1.img:addEventListener( "tap", runeTapped )

So basically exposing the display object by adding the .img let it all work fine.

It’s a bit hard to tell if you are doing this already from the code you have pasted, but when rune.new() is called it should be assigned to an object. E.g.

–in main.lua

local myNewRune = rune.new(200, 50, 50, true)

That way you have a reference to the object created by the new() function, which is what the isSelectable property was assigned to.

So in main.lua you would now be able to do this:

if (myRune.isPlayable) then myRune:addEventListener( "tap", listener ) end

Alternatively you could add the listener in your rune class, and then have a global “setter” function on main.lua which is called from that touch listener:

--main.lua local valueIWantToSetInMainDotLua = 0 function setMyValue(val) valueIWantToSetInMainDotLua = val end ---------------------------------------------------------------- --rune.lua local function listener(event) if event.phase == "ended" then setMyValue(event.target.value) end end rune:addEventListener("touch", listener)

The important thing there is that the function in main.lua does not have the word “local” in front of it, so it can be called anywhere. Generally not a great way of doing things, but handy if you have a function that needs to be called in lots of places.

Ah yes, you’re right, I did forget to include that part of the code. Yes in my main.lua file I have:

local rune1 = runes.new(1, display.contentCenterX - 100, display.contentCenterY + 100, true)

But even with that variable defined early on as soon as I launch the simulator I get the error

Attempt to call method ‘addEventListener’ (a nil value)

and it references this line

rune1:addEventListener( "tap", listener )

It doesn’t really give me much else on the error (at least not in the places I know to look, but I assume it’s a nil value because in runes.lua I haven’t defined addEventListener.

The issue may be I’m not sure how to overload a method in lua. 

I did try your second suggestion with the global function in main.lua. First I changed rune.new to look like this:

function rune.new(value, xPos, yPos, isPlayable) -- constuctor local newRune = display.newImageRect(runeSheet, value, 52, 52) newRune.x = xPos newRune.y = yPos newRune.value = value -- this is used both to determine which sprite shows up, but also the value of the rune for comparisson and game logic newRune.isPlayable = isPlayable if isPlayable then newRune:addEventListener( "tap", returnChoice ) end return setmetatable( newRune, rune\_mt ) end&nbsp;

but when I tried that, the project ran, but as soon as I tap on rune1 I get this error:

Attempt to call a nil value

I then just ignored the isPlayable part, and tried several different ways to add the event listener inside runes.lua right after the runes.new() function.

local runes = require("runes") local rune1 = runes.new(1, display.contentCenterX - 100, display.contentCenterY + 100) local currentChoice function setChoice(choice) if (currentChoice) then transition.scaleTo( currentChoice, {xScale = 1, yScale = 1, time = 100} ) end transition.scaleTo( thisRune, {xScale = 1.2, yScale = 1.2, time = 100} ) currentChoice = choice end

and my new runes.lua looked like this:

local rune = {} local rune\_mt = {\_index = rune} -- needed variables local runeInfo = require("runesSheet") local runeSheet = graphics.newImageSheet( "runes.png", runeInfo:getSheet() ) -- private functions local function returnChoice( event) setChoice(event.target) end -- public funtions function rune.new(value, xPos, yPos) -- constuctor local newRune = display.newImageRect(runeSheet, value, 52, 52) newRune.x = xPos newRune.y = yPos newRune.value = value -- this is used both to determine which sprite shows up, but also the value of the rune for comparisson and game logic return setmetatable( newRune, rune\_mt ) end self:addEventListener( "tap", returnChoice ) return rune

I got the error:

Attempt to index global ‘self’ (a nil value)

 

File: runes.lua

Line: 30

 

stack traceback:

runes.lua:30: in main chunk

[C]: in function ‘require’

?: in function <?:761>

(tail call): ?

main.lua:37: in main chunk

 

So line 37 is : local runes = require(“runes”)

and line 30 was where i was trying to add the event listener. Instead of self:addEventListener I also tried rune:addEventListener which gave me mostly the same error although this time it began with 

Attempt to call method ‘addEventListener’ (a nil value)

and then the rest was the same.

I also tried skipping the call to returnChoice, and just calling the global function setChoice but got the same errors.

Looking at https://docs.coronalabs.com/api/event/tap/index.html I’m guessing the problem has to do with the fact that although my customer class starts as a display object, it now has other stuff added on to it which seems fine if it’s all in main.lua but it doesn’t like being broken up. So how do I get to just the display object part of it?

So I figured it out eventually. It ended up being fairly simple. In my rune.new constructor I needed to set my newRune = {} and then create a newRune.img which was equal to my display item. So like this:

-- -- rune.lua -- local rune = {} local rune\_mt = {\_index = rune} -- needed variables local runeInfo = require("runesSheet") local runeSheet = graphics.newImageSheet( "runes.png", runeInfo:getSheet() ) -- private functions -- public funtions function rune.new(value, xPos, yPos, isPlayable) -- constuctor newRune = {} newRune.img = display.newImageRect(runeSheet, value, 52, 52) newRune.img.x = xPos newRune.img.y = yPos newRune.value = value -- this is used both to determine which sprite shows up, but also the value of the rune for comparisson and game logic newRune.isPlayable = isPlayable return setmetatable( newRune, rune\_mt ) end return rune

Then in my main.lua file I did this:

local runes = require("runes") local currentChoice local rune1 = runes.new(1, display.contentCenterX - 100, display.contentCenterY + 100, true) function runeTapped(event) local thisRune = event.target if (currentChoice) then transition.scaleTo( currentChoice, {xScale = 1, yScale = 1, time = 100} ) end transition.scaleTo( thisRune, {xScale = 1.2, yScale = 1.2, time = 100} ) currentChoice = thisRune return true end rune1.img:addEventListener( "tap", runeTapped )

So basically exposing the display object by adding the .img let it all work fine.