Using custom events to make game objects communicate

In my game objects communicate via custom events. I wrote a function called dispatchToAll which passes an event through all the objects on the stage. Objects handle that event and make decisions based on it. Kinda like traditional OOP approach. The problem arises when there is a non-graphic object that needs to catch events as well. There are two ways to solve this: 

  1. Simply make this non-graphic object a displayGroup and that’s it

2) Make a  stack of objects and manually add / remove objects to / from there

I would definetly prefer to go with #1 but my only concern is a waste of texture memory. Should it be a concern considering I’m only going to have about 2-3 such empty displayGroups?

@Soin08,

I use ‘custom’ events extensively in my apps and games.  Here is my approach (very simple):

First, I have shortcut code to make writing event code faster (I simply run this before my game code loads):

local getTimer = system.getTimer local pairs = \_G.pairs \_G.listen = function( name, listener ) Runtime:addEventListener( name, listener ) end \_G.ignore = function( name, listener ) Runtime:removeEventListener( name, listener ) end \_G.post = function( name, params, debuglvl ) local params = params or {} local event = { name = name } for k,v in pairs( params ) do event[k] = v end if( not event.time ) then event.time = getTimer() end Runtime:dispatchEvent( event ) end

Second, I use it like this:

local function onChangeColor( self, event ) -- Dummy code for this example: self:setFillColor( unpack(event.color) ) for k, v in pairs(event) do print(k,v) end end ... local tmp = display.newCircle( ... ) tmp.onChangeColor = onChangeColor listen( "onChangeColor", tmp ) ... post( "onChangeColor", { color = {1,1,0} } ) -- Circle changes to YELLOW ... post( "onChangeColor", { color = {0,1,1} } ) -- Circle changes to CYAN ignore( "onChangeColor", tmp ) -- Stop listening for event post( "onChangeColor", { color = {1,1,1} } ) -- Circle stays CYAN since it isn't listening.

Finally, removing the object kills the listener automatically so this code is safe and easy to work with. 

Note: I usually have cleanup code in my listeners however for extra safety like this:

-- Safer listener local function onChangeColor( self, event ) if( self.removeSelf == nil) then -- Not a display object any more, probably got deleted/removed ignore( "onChangeColor", self ) end -- Dummy code for this example: self:setFillColor( unpack(event.color) ) for k, v in pairs(event) do print(k,v) end end

@Roaminggamer,

Your approach is much better than the one I’m currently using and it eliminates my problem completely. Thanks a lot!

@Roaminggamer,
 
“removing the object kills the listener automatically so this code is safe and easy to work with” – This is true for all the listeners attached to the object that gets removed, however I don’t find it to be true in our case, when all the listeners are attached to the Runtime.
 
Please check out this example:
[lua]local group = display.newGroup()
function group:myEvent()
    print(“group:myEvent”)
end
listen(“myEvent”, group)
group:removeSelf()
group = nil
timer.performWithDelay(3000, function()
    post(“myEvent”)
end)
[/lua]

Here is my little addition in case we need to remove all Runtime listeners of an object (I do it for an object before it gets removed)

[lua]

_G.ignoreAll = function(obj)

    for k, v in pairs(Runtime._tableListeners) do

        for i = #v, 1, -1 do

           – if v[i] == obj then v[i] = nil end BUG!  Should use table.remove like below

              if v[i] == obj then table.remove(v, i) end --works fine

        end

    end

end

[/lua]

UPDATE:

Silly me, forgot to use table.remove. I commended the bug and now the code is fully working.

Thanks for the addition!

@Soin08,

I use ‘custom’ events extensively in my apps and games.  Here is my approach (very simple):

First, I have shortcut code to make writing event code faster (I simply run this before my game code loads):

local getTimer = system.getTimer local pairs = \_G.pairs \_G.listen = function( name, listener ) Runtime:addEventListener( name, listener ) end \_G.ignore = function( name, listener ) Runtime:removeEventListener( name, listener ) end \_G.post = function( name, params, debuglvl ) local params = params or {} local event = { name = name } for k,v in pairs( params ) do event[k] = v end if( not event.time ) then event.time = getTimer() end Runtime:dispatchEvent( event ) end

Second, I use it like this:

local function onChangeColor( self, event ) -- Dummy code for this example: self:setFillColor( unpack(event.color) ) for k, v in pairs(event) do print(k,v) end end ... local tmp = display.newCircle( ... ) tmp.onChangeColor = onChangeColor listen( "onChangeColor", tmp ) ... post( "onChangeColor", { color = {1,1,0} } ) -- Circle changes to YELLOW ... post( "onChangeColor", { color = {0,1,1} } ) -- Circle changes to CYAN ignore( "onChangeColor", tmp ) -- Stop listening for event post( "onChangeColor", { color = {1,1,1} } ) -- Circle stays CYAN since it isn't listening.

Finally, removing the object kills the listener automatically so this code is safe and easy to work with. 

Note: I usually have cleanup code in my listeners however for extra safety like this:

-- Safer listener local function onChangeColor( self, event ) if( self.removeSelf == nil) then -- Not a display object any more, probably got deleted/removed ignore( "onChangeColor", self ) end -- Dummy code for this example: self:setFillColor( unpack(event.color) ) for k, v in pairs(event) do print(k,v) end end

@Roaminggamer,

Your approach is much better than the one I’m currently using and it eliminates my problem completely. Thanks a lot!

@Roaminggamer,
 
“removing the object kills the listener automatically so this code is safe and easy to work with” – This is true for all the listeners attached to the object that gets removed, however I don’t find it to be true in our case, when all the listeners are attached to the Runtime.
 
Please check out this example:
[lua]local group = display.newGroup()
function group:myEvent()
    print(“group:myEvent”)
end
listen(“myEvent”, group)
group:removeSelf()
group = nil
timer.performWithDelay(3000, function()
    post(“myEvent”)
end)
[/lua]

Here is my little addition in case we need to remove all Runtime listeners of an object (I do it for an object before it gets removed)

[lua]

_G.ignoreAll = function(obj)

    for k, v in pairs(Runtime._tableListeners) do

        for i = #v, 1, -1 do

           – if v[i] == obj then v[i] = nil end BUG!  Should use table.remove like below

              if v[i] == obj then table.remove(v, i) end --works fine

        end

    end

end

[/lua]

UPDATE:

Silly me, forgot to use table.remove. I commended the bug and now the code is fully working.

Thanks for the addition!

That is very very clever

I just wanted to drop a quick mention of the “object:dispatchEvent()” API into this thread, whether or not it relates directly to what you want to do. This API can be an unsung hero, but few people seem to even know it exists:

https://docs.coronalabs.com/api/type/EventListener/dispatchEvent.html

The reason I like this is because it’s extrememly customizable. You can basically make anything into an “event” and pass any number of custom parameters along with it, then conditionally check those parameters in the listener function. I urge you to check it out, experiment with it, and see how useful it can truly be.

Brent

Hi Brent,

I did see that in my search, but unless I am mistaken, it requires an object to be the sender, look at the last line in the example.

The example is also a bit confusing, because the sender is the same object that is the listener, sure it works, but i think normally it would be a *different* object… but anyways…

That solution by that roaminggamer guy does not require an object to be the sender. You can imaging that oft times, you would want to send an event to several objects, but the sending of the message is not related to a display object. Does it make sense? 

why don’t you just steal his code and put it in the next release.

 

Don’t forget that you can also dispatch events to the Runtime, and have function then do what is necessary, e.g. by going over a table containing all your enemy objects and deleting them.

For your information: I have wasted tons of time by trying to have objects and events all communicate with eachother directly without keeping tracks of them in lists. It was, in the end, much better to keep track of things in lists and table, and work with those.

I’ve used this method to implement event dispatching for my Corona client mods, but I’m pretty sure it’s specific to display objects. I basically set up an empty group and use it as a dispatcher.

Now, if could we have a real Emitter class to work with, that would be the best. We have high level GPU rendering possibilities, but no ‘real’ Emitter class (non-graphic).  :ph34r:

I’m not a huge fan of using the Runtime listener, but it’s sometimes the only option internally.

Anyway, I use this all the time now.  https://github.com/daveyang/EventDispatcher

Perhaps it will be helpful to others.

Cheers.

What do you mean Emitter class? Are you talking about particle emitter or “event” emitters? If it’s the first, why not create your own using some light OOP?

I actually prefer the Runtime listeners. It allows me to keep most of my event-based functions in one module that contains the code to communicate with all of the other objects.

It’s interesting to see how these talks wind and weft.

I’ve yet to investigate Chris’ suggestion, but for anyone reading this, my code above is basically a short-hand wrapper for the Runtime:dispatchEvent() and related features.

I prefer it because it is shorter and clearer:

listen( "eventName", obj ) -- vs Runtime:addEventListener( "eventName", obj ) ignore( "eventName", obj ) -- vs Runtime:removeEventListener( "eventName", obj ) post( "eventName", { more = "data", evenMore = 1 } ) -- vs Runtime:dispatchEvent( { name = "eventName", more = "data", evenMore = 1 } ) 

Hi,

Semi-relevant still I think…I put together this mod called Eventable.  It’s like a chat room for your tables and mods. Any testing would be appreciated. It’s pretty alpha though.

http://develephant.github.io/Eventable/

Cheers.

That is very very clever