Organising larger projects, a simple game state engine [ed: with event handler]

I wanted to ask what kind of game state engines people have come up with for larger projects. Initially I had a set of variables defined:

game={splash=1, playing=2, paused=3, over=4, level=5, edit=10}
game.state=game.splash

And had branching if structures in Draw() , Touched() etc.
But then I had the idea of storing separate routines in a table indexed by the game state value, like this:

game={splash=1, playing=2, paused=3, over=4, level=5, edit=10} --must be declared outside of a function.
touchBegan={} 
touchBegan[game.playing]=function(args)
...

dr={} --Codea won't allow a table called draw
dr[game.over]=function(args)
...

Then to call the routines, you don’t need an if structure, just touchBegan[game.state] or dr[game.state]

Interestingly, you have to declare these with variable=function(arguments). If you try this, Codea won’t compile it:

function touchBegan[game.playing](arguments) --won't work

How do you setup your game state engines? Another potential structure I haven’t explored in my own code yet is the way the different tests are called in the physics debug example, with each function name held in a table.

I found this scenemanager class, by @Frosty, to be extremely intuitive and it helped me massively.

http://codea.io/talk/discussion/677/scenemanager/p1

Each screen you encounter in the game is then defined by a class, and you can perform specified functions just before a new screen is called, or on exiting that screen, and the draw / touched functions are automatically segregated.

Bear in mind I’m a real beginner and I expect others here will have more sophisticated ways of doing things. This is a good thread to start!

@yojimbo2000 I have a fairly large game thats uses if statements in the my draw, predraw and touched functions. It works fine, my touched function is a complete mess tho.

That Scene Manager is similar to how Corona does it, so is probably a good way to go.

@Goatboy76 yeah, I was also using large branching if structures. But having blocks of code spanning hundreds of lines, indented several layers deep, is so messy. When you’re editing an if block, adding some elseifs or whatever, I was always losing the end.

This is Cargobot’s touched loop. I’d love to get as minimal as this.

 
function touched(t)
    if currentScreen and currentScreen.touched then currentScreen:touched(t) end
end

Thanks for the scene manager link. I don’t really understand it though. In the example, he defines an example menuscene(), but it doesn’t refer to the scenemanager() class?

in CodeaShowcase i have in th Main:

function touched(t)
    if Screen then Screen:touched(t) end
end

in th screen class i have

function Screen:touched(t)
    if not currentScreen or not currentScreen.touchable then return end
    currentScreen:trigger("touched",t)
end

the trigger mechanism provided by the Event class makes extremely simple to build complex project by decoupling things. If you wànt to be that simple, look for ‘event’ discussions on the forum. At first it sounded difficult to me but after understanding it is really simple. And mandatory.

@yojimbo2000 If its spanning hundreds of lines then you should break it down into different functions for related tasks.

I’ve created my own heavyweight screen manager library which is inspired by the Corona storyboard lib, plus some of my own additions. It’s fully object oriented and supports transitions and event callbacks and forms the base for a complete ui / widget library. I’ve already used it in one released app and it’s running at least two other prototypes as well.

The screen manager part is done but really needs properly documenting. I was going to post it when the widget library is a bit more complete but if you want a copy then drop me a PM (I’ll see if I can get it sorted on my GitHub account)

@Jmv38 thanks for the tip about searching for event managers, there are some really interesting discussions there. I still find them hard to follow, I think because the event managers are all presented as templates (understandably), but it would be nice to see how they would work in a simple use case.

For instance, at the moment my code is clogged with the tutorial system I have. The idea is that as the player plays the first level, alerts are triggered at various points telling them how to play. This means that the code is punctuated with tests like this (this one is in the collision routine):

if lev==1 and tut.extra==true and tut.now==false and tut.goal==false then
                tut.now=true
                tut.goal=true
                tween.delay(0.8,function() tutorial("Get the gem meter\
over the target line\
to clear the level")end)
            end


```


the `if` statement is saying, if this is level 1  (`lev=1`), and the previous tutorial has already printed (ie they have to occur in a given order `tut.extra`), and there isn't currently an alert onscreen ( ie to prevent them piling up on top of oneanother `tut.now`), and this particular event has not triggered before (ie this is once only `tut.goal`), then activate the `tutorial` function (after a brief delay, so that the player can register the triggering event), which displays the text in a fancy way, and waits for a user tap to clear it.

Aside from looking ugly, and lengthening the code block, and scattering the tutorial texts throught the code, and being difficult (even for me) to read, and being easy to break, it means that an `if` statement is called every time this particular type of collision occurs, for what is a once-only event, which doesn't seem like a good use of resources.

I understand the principle that with an event manager system I could uncouple the tutorial code from the collision code, so that one triggers the other without the kind of ugly direct link that I have here. But I'm struggling to see how I would implement this using the various event manager templates I've seen, or even work out how "heavy weight" a manager I would need to implement this kind of system.

i cant answer that in a couple of lines. I think in codeaShowcase i ended up with very complex behavior, a bit like what you need. But it is not coded as you did, it would be impossible this way. The key is maybe:

1- think ‘objects’: an object is defined by a class, has some methods and properties. Not too many. Less than 100 lines is a good limit. If you need more, then can the object have some other objects as properties? So each object remains understandable. Use ‘getters’ and ‘setters’ to change their properties, this will pay when you start modifying your code from its first version (happens quite quickly). When the *has a" relationship does not make sense, you can use a “is a” relationship, by building a class from another one. But this is more complex. If you look at cargobot, you will see many classes piled up, each adding a few things. It is much simpler to understand, in the long run, than putting everything in the same long class definition tab.

2- make objectA be an event broadcaster of “jump” event and objectB listen to these events to run objectB:jump(), rather than directly calling the objectB:jump() from objectA. Dont do it when the relation is systematic with all objects known in advance. Do this when it makes sense: when there may be several impacts of “jump”, but you are not sure how many, etc. This is a huge relief for the brain…

Defining the good breakdown into objects, or when to use events, is not obvious, it requires some first attemps of coding, then meditation, then rewriting the code completely (many times). For instance, in my buttons library first various style properties were directly in the object. But there were so many i put them into a table button.stlye. Then i had various objects with a style property, and it became obvious that is was simpler to define a Style() class and object, and then i changec for button.style = Style(). On the other hand i’ve always kept x,y,w,h instinsic properties of a button, because that seems natural, and the access is faster with button.x than button.position.x, and i thought it is important for speed. I still hesitate and may change my mind on that.

Maybe i’ll try to make an example from your description, but this is going to require some thinking, because i have not implemented collisions yet.

PS: when i say ‘think object’, i really mean ‘think’. it is difficult. It takes time. It took me about 2 years to switch from ‘procedural’ thinking to ‘oop’ thinking. You can find several discussions on the forum i started under the subject ‘how to make big projects?’. I was always hitting a ‘complexity wall’, and now i’ve passed through it, via the above concepts.

@yojimbo2000 - I agree with Jmv38, evolve your code. I don’t think you need sophisticated event management at this stage, though.

I would start by getting the tutorial code out of the setup, draw and touched functions, and putting it into its own set of functions. What setup, draw and touched should do is to pass these functions the information they need to decide what to do - and that is all.

For example, the touched function might run Tutorial("touch",level).

The tutorial code could include a table of help messages in sequence, have a counter that keeps track of where you’re up to, and a function (called by the main draw function) that puts the current message on the screen. You can queue messages by putting them in a table, so that as one finishes, it gets deleted from the table and the next one is shown.

When you’ve got it working, I’d think about how to make it efficient, so you don’t waste a lot of time processing tutorial code once they’ve been shown.

This needn’t be very complex code, IMHO.

Here is an example with some of the ingredients you want.
Events are used for drawing various objects, with collisions, delays.
[EDIT] i’ve added a couple buttons and a quiet state to show how to trigger a global status (like your tuto / not tuto). You can see that all the state impacts are resolved locall by each object.
[EDIT] i’ve changed the shout mechanism so teach shout generates next (you wanted an sequence of events in a given order).
[EDIT] modified again: you can tap the ball, so you see the touched events too.



--# EventMngr
-- ############## START of EVENT MANAGER ##################
-- @tnlogy & @JMV38 & @Briarfox
-- example of usage:
--    EventMngr:extend(evMngr)           -- extend an existing table with event manager funcs
--    evMngr:on("touch",func)            -- register func() to fire on "touch" event
--    evMngr:on("touch", obj.func, obj)  -- register obj:func() to fire on "touch" event
--    evMngr:trigger("touch",10,50)      -- fires func(10,50) and obj:func(10,50)
--    evMngr:off("touch", func)          -- unregister func()
--    evMngr:off("touch", obj.func, obj) -- unregister obj:func()
--    evMngr:off("touch")                -- unregister all "touch" listeners
--    evMngr:off(obj.func)               -- unregister all listeners with obj.func
--    evMngr:off(obj)                    -- unregister events with obj listening
--    "all" captures all events and passes the event name as the first param:
--    evMngr:on("all", func) 

EventMngr = {}

local fifo = true -- first in (to register) first out (to be triggered)
function EventMngr:on(eventName, fn, obj)

    if not self.events then self.events = {} end -- init event table if does not exist
--    if not self.events[eventName] then self.events[eventName] = {} end -- init this event name
    if not self.events[eventName] then self.events[eventName] = {} end -- init this event name
    
    local new = true -- confirm it is a new request
    for i,fa in ipairs(self.events[eventName]) do
        if fa.func == fn and fa.obj == obj then new = false end
    end
    
    local p -- insertion point in the table
    if new then 
        if fifo then p = #self.events[eventName] +1 else p = 1 ; fifo=true end
        local listener = {func = fn, obj = obj }
        table.insert(self.events[eventName], p, listener) 
    end

    return self
end

function EventMngr:executeNextCallBeforeOthers()
    fifo = false
end

function EventMngr:off(nameOrFnOrObj, fn, obj)
    local name
    local fn,obj = fn,obj -- manage the case when they are nil
    local firstType = type(nameOrFnOrObj)
    local request
    if firstType == "string" or firstType == "number" then 
        name = nameOrFnOrObj
        if name == "all" then request = "remove all events"
        elseif fn == nil then request = "remove all instances of this event"
        else request = "remove this event" end
    elseif firstType == "function" then 
        fn = nameOrFnOrObj
        request = "remove all events with this function"
    else 
        obj = nameOrFnOrObj
        request = "remove all events with this object" 
    end

    if request == "remove all instances of this event" then
        self.events[name] = nil
    elseif request == "remove all events" then
        self.events = {}
    else
        local evs = self.events            -- go through all events ...
        if name then evs = {evs[name]} end -- ... or through 1 event only
        for eventName,fns in pairs(evs) do
            local n = #fns
            for i=0,n-1 do
                local j = n-i  -- go backward because of remove, ipairs not suitable
                local f = fns[j] 
                local match
                if request == "remove this event" 
                then match=(f.func==fn and f.obj==obj)
                elseif request == "remove all events with this function" 
                then match=(f.func==fn)
                elseif request == "remove all events with this object" 
                then match=(f.obj==obj)
                end
                if match then 
                    table.remove(fns,j) 
                end
            end
        end
    end
    return self
end

function EventMngr:trigger(name, ...)
    self.lastTrigger = name
    local evs = (self.events and self.events[name]) or {}
    for i,fa in ipairs(evs) do 
        local func,obj = fa.func, fa.obj
        if obj then func(obj,...) 
        else func(...) end
    end
    --trigger all
    local evs = (self.events and self.events["all"]) or {}
    for i,fa in ipairs(evs) do 
        local func,obj = fa.func, fa.obj
        if obj then func(obj,name,...) 
        else func(name,...) end
    end
end
        
-- to transform a table into an event manager
function EventMngr:extend(target)
    for k, v in pairs(self) do
        if type(v) == "function" and v ~= EventMngr.extend
        then target[k] = v 
        end
    end
    return target
end


-- ############## END of EVENT MANAGER ##################




--# Noise
Noise = class()
EventMngr:extend(Noise)
function Noise:init(b)
    self:setSilence(b)
end

function Noise:setSilence(b)
    self.silence = b
    self:trigger("silence",self.silence)
end

Noise:init(false)
--# World
World = class()
EventMngr:extend(World)

function World:init()
end

function World:draw()
    self:trigger("draw")
end

function World:touched(touch)
    self:trigger("touched", touch)
end

--# Message
Message = class()

local enabled = true

Noise:on("silence",function(b) enabled = not b end)

function Message:init(txt,x,y,t,size)
    if not enabled then return end
    self.x, self.y, self.t, self.txt, self.size = x,y,t,txt, (size or 50)
    World:on("draw",self.draw,self)
    tween.delay(self.t,function() World:off(self) end)
end

function Message:draw()
    fill(255, 0, 0, 255)
    fontSize(self.size )
    text(self.txt, self.x, self.y)
end


--# Edge
Edge = class()
EventMngr:extend(Edge)

function Edge:init(data)
    if data.x then
        local x = data.x
        self.pos0 = vec2(x,0)
        self.pos1 = vec2(x,HEIGHT)
    elseif data.y then
        local y = data.y
        self.pos0 = vec2(0, y)
        self.pos1 = vec2(WIDTH, y)
    else
        error("please define x or y")
    end
    self.body = physics.body(EDGE, self.pos0, self.pos1)
    self.body.info = self
    -- events
    World:on("draw",self.draw,self)
    World:executeNextCallBeforeOthers()
    World:on("touched",self.touched,self)
end

function Edge:draw()
    fill(223, 223, 223, 255)
    line(self.pos0.x, self.pos0.y, self.pos1.x, self.pos1.y)
end

function Edge:touched(touch)

end

--# Ball
Ball = class()

function Ball:init(x,y,r)
    self.x = x
    self.y = y
    self.r = r
    self.body = physics.body(CIRCLE, self.r)
    self.body.x = self.x
    self.body.y = self.y
    self.body.sleepingAllowed = false
    self.body.gravityScale = 0
    self.body.linearVelocity = vec2(400,200)
    self.body.linearDamping = 0
    self.body.angularDamping = 0
    self.body.friction = 0
    self.body.restitution = 1
    self.body.info = self
    -- events
    World:on("draw",self.draw,self)
    World:executeNextCallBeforeOthers()
    World:on("touched",self.touched,self)
    print ("try to tap the ball")
end

function Ball:draw()
    local pos = self.body.position
    self.x, self.y = pos.x, pos.y
    fill(223, 223, 223, 255)
    ellipse(self.x,self.y,self.r*2)
end

function Ball:shout(size)
    self.soundSize = size or self.soundSize or 50
        -- this one
        Message("A",self.x,self.y,1,size)

        -- next one
        local newSize = self.soundSize * 0.9
        if newSize > 20 then
            tween.delay(0.05,function()self:shout(newSize)end)
        else 
            collectgarbage()
        end
end

function Ball:collide(c)
    self:shout(50)
end

local abs = math.abs
function Ball:touched(t)
    if abs(self.x-t.x)<self.r and abs(self.x-t.x)<self.r then
        if t.state == BEGAN then 
            Message("Outch!",self.x,self.y,1,50)
        end
    end
end

--# Main
-- eventsExample

-- Use this function to perform your initial setup
function setup()
    e1 = Edge({x=0})
    e2 = Edge({x=WIDTH})
    e3 = Edge({y=0})
    e4 = Edge({y=HEIGHT})
    b = Ball(WIDTH/2, HEIGHT/2, 50)
    parameter.action("silence",function() Noise:setSilence(true) end)
    parameter.action("loud",function() Noise:setSilence(false) end)
end

-- This function gets called once every frame
function draw()
    background(40, 40, 50)
    strokeWidth(5)
    World:draw()
end

function touched(t)
    World:touched(t)
end

function collide(c)
    if c.state == BEGAN then 
        fA = c.bodyA.info.collide
        if fA then fA(c.bodyA.info, c) end
        fB = c.bodyB.info.collide
        if fB then fB(c.bodyB.info, c) end
    end
end

What is interesting in the code above, is that it was extremely easy to program: it worked right away, thanks to objects / events. Try to achieve the same thing with 'if xxx and zz and yyy then … ', it would be a real headache!
[EDIT] and i’ve modified it several times since first post: it was really easy to add, remove of change structure for various actions. No headache, no bug… that’s almost incredible. That was not possible with my former procedural way to code.

@Jmv38 Thank you very much for that, I’m working through your code, I don’t understand it all yet, but it’s got some fascinating parts.

I’ve also been working on an adaptation of the CargoBot events handler. I basically took out everything I don’t (yet) understand, so that it’s very simple, but also changed a few things based on how Corona handles events (or at least my understanding of Corona).

What really helped me get my head round the concepts is this tutorial for Corona here:

http://www.omidahourai.com/from-zero-to-oo-ardentkids-guide-to-object-oriented-lua-with-corona-sdk#customevents

As events are built-in to Corona this tutorial doesn’t say anything about what Corona does under the hood, but you can see the syntax and usage.

So what I decided to experiment with was to make my Events class a super-class of all the scenes in the game. It mimics the Corona usage of:

scene:subscribe(“event name”, listener, callback)

The advantage of this method is it becomes very easy to manage a large set of events, without explicitly having to handle them in the event manager eg, a “pause” state which temporarily suspends all of the events can be done with one line.

@Jmv38 the method you use instead, of extending a table, is really interesting though, and not something I’d encountered before. I need to study your approach more, as I suspect it’s more powerful and flexible. The advantage of the one I’m working on is that it’s very light (the event manager is 30 lines). I’ll post mine in a few moments, so people can compare

I’ll have to split it over 2 comments. I’ll put it on Codea Community too. Feedback welcome! [EDIT 1.06 replaced scenes table with a set of variables for more readable, unambiguous code, and to prevent redundant copies of scenes being added to a scene table. Added separate objectInit routine to avoid having to pass the event scope to every object class.]


--# Main
--[[
RETURN TO THE GEOSPHERE 
By Utsira
a simple stealth game serving as a proof of concept for a light modular game design, using class inheritance and an event manager to decouple the code, allowing extra objects and states to be added with very little extra code. There are two strands of class inheritance (matching left-to-right organisation of tabs): 
1) SCENES or states, which also houses the event manager as a superclass, so that events are restricted to the scope of the scene:
Event > (Scene > (Splash, Game, GameOver, Pause))
2) OBJECTS:
Object > (Button > (PauseButton), WorldObject > (Tree, Goal, Agent > (Hero, AI > (Bug))))
  ]]

displayMode(OVERLAY)

function setup()
    centre=vec2(WIDTH/2,HEIGHT/2)
    difficulty=3
    font("DINAlternate-Bold")
    fill(31, 31, 95, 255)
    fontSize(30)
    textMode(CENTER)
    textAlign(CENTER)   
    splash=Splash()   --variable "scene" automatically set whenever a scene is defined
    
   -- fps.init()    
end

function draw()
    sprite("SpaceCute:Background", centre.x, centre.y, WIDTH, HEIGHT)
    scene:draw()
end

function touched(touch)
  if not scene.delay then scene:dispatch("touched", touch) end
end

--# Event
Event = class()
--[[
very simple event handler, adapted from Cargobot, but also drawing on Corona.
Event is made a superclass of Scene, and therefore of every scene in the game.
This means that events have a scope, the scene they belong to,
a concept borrowed from Corona.
This greatly helps modularity. For an example look at how PauseButton & Pause are able to suspend, remember, and recall a set of events with one-line calls to table.insert and remove

syntax:
-------
scene:subscribe(event, listener, callback)
scene:unsubscribe(event, listener) --listener optional
scene:dispatch(event, arguments)

examples:
---------
scene:subscribe("catMoves", self, self.chase) 
    --a bug subscribes to event "catMoves"
scene:dispatch("catMoves", self.pos) 
    --broadcast cat position from hero class to current scene
self:subscribe("touched", self, self.touched) 
    --a scene subscribes itself to a system event, note repetition of self

notes:
------
1. the callback is defined in the class in the usual way, ie with a colon
"Bug:chase(arg)", but is given as a callback argument with a period, 
and without brackets or arguments "self.chase"
2. if the scene init() defines objects that subscribe to events, those objects won't be able to access the scene variable in their own init() routines, because the scene is still in the process of being defined. So you need to pass "self" as an argument from the scene init() to the object init() if you want those objects to be able to subscribe to events
3. Roadmap: Currently dispatch needs to be called every frame for anything to happen, not the best use of resources. A better system would be an on off flag for dispatch, a callbackOn and callbackOff in subscribe, and a state saver at either end.
4. Issues: order of listeners currently random, see note below
]]
function Event:init()
    self.events={}
    print("Scene "..self.id.." created\
"..string.rep("=",34))
end

function Event:subscribe(event, listener, callback)
    if not self.events[event] then
        self.events[event]={} --create event
        print ("Event "..event.." created in "..self.id.."\
"..string.rep("-",34))
    end
    self.events[event][listener]=callback --add listener. Problem: no way of controlling order, depends on random memory address. So cant set order of listeners, eg if using event as draw loop
    print (listener.id.." is subscribed to "..event) 
end

function Event:unsubscribe(event, listener) --listener optional
    if listener then
        self.events[event][listener]=nil --unsubscribe one listener
        print (listener.id.." unsubscribed from "..event) 
    else
        self.events[event]=nil --unsubscribe all listeners
    end
end

function Event:dispatch(event,...)
    if not self.events or not self.events[event] then
      --  print ("no subscribers to "..event) --error message for debug purposes
        return
    end
    for listener,callback in pairs(self.events[event]) do
        callback(listener, unpack(arg))
    end
end

--# Scene
Scene = class(Event)

function Scene:init()
    Event.init(self)
    self.time=ElapsedTime
    self.delay=0
    self.objects={}
    self.note="Tap anywhere to continue"
    scene=self --automatically switch scope when defining a scene
end

function Scene:draw() --this is used by gameover and pause....
    game:dispatch("draw") --to draw the underlying game scene
    self:wait()
end

function Scene:touched(touch) --used by gameover and splash
    game=Game() --(re)boot game
    game:initObjects()
end

function Scene:wait() --handles timeout at start of scenes
    fontSize(30)
    if self.delay then
        text(string.format(self.note.."\
in %.1f", self.delay-(ElapsedTime-self.time)), centre.x, centre.y-200)
        if self.time<ElapsedTime-self.delay then
            self.delay=nil
        end
    else 
        text(self.note.."\
", centre.x, centre.y-200 ) --"Tap anywhere to continue\
"
    end
end

--# Splash
Splash=class(Scene)

function Splash:init()
    self.id="splash"      
    Scene.init(self)    
    self.note="A super simple stealth game.\
Hide behind trees to fool bugs.\
\
"..self.note
    self:subscribe("touched", self, self.touched)
end

function Splash:draw()
    fontSize(50)
    text("Return to the\
Geosphere!", centre.x,centre.y+100)
    self:wait()
end

--# Game
Game=class(Scene)

function Game:init()
    self.id="game"
    Scene.init(self)
    self.delay=2
   -- parameter.action("test if objects are listening", function() self:dispatch("testButton") end)
    self.note="Level "..(difficulty-2).."\
Ready to go"
end

function Game:initObjects() --objects need their own setup so that they can access the scene/game var and define events                  
    self.button=PauseButton(1)            
    self.hero=Hero(vec2(WIDTH-50,50))  
    self.worldObjects={ Goal(vec2(200,HEIGHT-50))} --self.worldObjects used for collisions ...
    for a=1, math.max(1, 20-(difficulty*0.4)) do --trees need to be before bugs...
        table.insert(self.worldObjects, Tree())   -- for hiding to work properly....     
    end --no draw loop
    for a=1, difficulty do 
        table.insert(self.worldObjects, Bug()) 
    end  
end

function Game:draw()
    if self.delay then 
        self:wait()
    else
        self:dispatch("move")
    end
    self:dispatch("draw")
  --  fps.draw()
end

--# GameOver
GameOver = class(Scene)

function GameOver:init(won)
    self.id="game over"
    Scene.init(self)    
    if won then
        difficulty = difficulty + 1
        self.note="YOU REACHED THE GOAL!\
Tap anywhere\
to go to next level"
    else
        self.note="GAME OVER!\
Tap anywhere\
to retry level"
    end
    self.delay=2  
    self:subscribe("touched", self, self.touched)
end

--# Pause
Pause = class(Scene)

function Pause:init()
    self.id="pause"
    Scene.init(self)
    self.delay=2
    self.note="PAUSED\
"..self.note
    self:subscribe("touched", self, self.touched)
end

function Pause:touched(touch)
    game.delay=2 --delay game when it restarts
    game.time=ElapsedTime
    game.note="Resume"
    scene=game --switch scope to game
end


```

Part 2 [EDIT 1.06 replaced scenes table with a set of variables for more readable, unambiguous code, and to prevent redundant copies of scenes being added to a scene table. Added separate objectInit routine to avoid having to pass the event scope to every object class.]:

--# Object
Object = class() --helper class for everything drawn in game...

function Object:init(pos)
    self.pos=pos or vec2(math.random(0, WIDTH-100),math.random(100, HEIGHT))
    self.width,self.height=spriteSize(self.img)
    scene:subscribe("draw", self, self.draw) --...subscribes to draw
end

function Object:draw()
    sprite(self.img, self.pos.x, self.pos.y, self.width)
end

function Object:touched(touch)
    if touch.xself.pos.x-self.width*0.5 and touch.yself.pos.y-self.height*0.5 then
        self:action(touch)
    end
end

function Object:testButton()
    print(self.id.." hears button being pressed")
end

--# Button
Button = class(Object)

function Button:init(number)   
    self.id="button"
    Object.init(self)
    self.pos.x=WIDTH-self.width
    self.pos.y=HEIGHT-(self.height*number)
    scene:subscribe("touched", self, self.touched)
end

--# PauseButton
PauseButton = class(Button)

function PauseButton:init(number)
    self.img=readImage("Cargo Bot:Step Button")
    Button.init(self, number)
end

function PauseButton:action(touch)
    if touch.state==BEGAN then pause=Pause() end 
end

--# WorldObject
WorldObject = class(Object) --everything in the game world that can be collided with

function WorldObject:init(pos)
    Object.init(self, pos) 
    self.width=self.width*0.5 --scale down objects in world
    self.height = self.height * 0.5
    self.radius=math.min(self.width, self.height)*0.4 --...used for (generous) collision detection
end

--# Tree
Tree = class(WorldObject)

function Tree:init(pos)
    self.id="tree"   
    self.img=readImage("Planet Cute:Tree Tall")    
    WorldObject.init(self, pos)
end

--# Goal
Goal = class(WorldObject)

function Goal:init(pos)
    self.id="goal"
    self.img=readImage("SpaceCute:Planet")
    WorldObject.init(self, pos)
end
--# Agent
Agent = class(WorldObject) --anything that moves...

function Agent:init(pos)
    WorldObject.init(self, pos)
    self.angle=0
    self.delta=vec2(0,0)
     --   self.bound={a=vec2(-w,-h), b=vec2(WIDTH+w,HEIGHT+h)}
    self.bound={a=vec2(0,0), b=vec2(WIDTH, HEIGHT)}
    scene:subscribe("move", self, self.move) --...subscribes to move
end

function Agent:move()
    --bounds
    if self.pos.x>self.bound.b.x then
        self:outBounds("x", self.bound.b.x)
    elseif self.pos.xself.bound.b.y then
        self:outBounds("y", self.bound.b.y)
    elseif self.pos.y<self.bound.a.y then 
        self:outBounds("y", self.bound.a.y)
    end
    --move, decelerate
    self.pos = self.pos + self.delta
    self.delta = self.delta * 0.75
    
end

function Agent:draw()
    pushMatrix()
    translate(self.pos.x, self.pos.y)
    rotate(math.deg(self.angle))
    sprite(self.img,0,0,self.width)
    popMatrix()    
end

function Agent:outBounds(axis,limit)
    self.pos[axis]=limit
    self.delta[axis]=-self.delta[axis]
end

--# Hero
Hero = class(Agent) --this is you

function Hero:init(pos)
    self.id="cat girl"    
    self.img=readImage("Planet Cute:Character Cat Girl")    
    self.speed=15
    Agent.init(self, pos)
    scene:subscribe("touched", self, self.touched)      
end

function Hero:move()
    Agent.move(self)    
    --detect collisions
    self.hiding=false
    for a,b in ipairs(scene.worldObjects) do
        if self.pos:dist(b.pos)<(self.radius+b.radius) then
            if b.id=="goal" then
                gameOver=GameOver(true) --win flag set to true
                return --exit collision loop
            elseif b.id=="tree" then
                scene:dispatch("catMoves", nil) --conceal cat position
                self.hiding=true
            elseif b.id=="evil bug" and not self.hiding then
                gameOver=GameOver()
                return --exit collision loop
            end
        end
    end
end

function Hero:touched(touch) --try changing touched to action to turn off offset handling
    self.delta=vec2(touch.deltaX,touch.deltaY) 
  --  self.pos=vec2(touch.x,touch.y)
    if not self.hiding then scene:dispatch("catMoves", self.pos) end --broadcast cat pos 
end

--# AI
AI = class(Agent)

function AI:init(pos)
    self.seed=math.random(1000)
    self.mult=1.5 + math.random() * 1.5
    self.turn=0
    Agent.init(self, pos)
end

function AI:calculate() --produce organic seeming quasi-random movement
    local pixel = noise((self.seed+self.pos.x)*.005, (self.seed+self.pos.y)*.005)
    local brightness = (pixel+1) / 2 * self.mult
    self.speed = brightness * self.mult * (1+(difficulty*0.05)) 
    self.angle = brightness * 360 * math.pi / 180 + self.turn
end

function AI:outBounds(axis, limit) --turn bug round when it gets to bound
    self.pos[axis]=limit
    self.seed = self.seed - 0.01
    self.turn = self.turn - math.pi/100
end
                       
--# Bug
Bug = class(AI)

function Bug:init(pos)
    self.id="evil bug"        
    self.img=readImage("Planet Cute:Enemy Bug")
    self.speed=difficulty*2 
    scene:subscribe("catMoves", self, self.chase)
  --  current:subscribe("catMoves", self, function(target) self.target=target end) --nb anonymous functions work, tho not sure how you pass args to them
     --   scene:subscribe("testButton", self, self.testButton)
    AI.init(self, pos)
end

function Bug:move()
    if self.target then
        local diff=self.target-self.pos
        self.angle=math.atan2(diff.y,diff.x)
    else
        AI.calculate(self)
    end
    self.delta.x = math.cos(self.angle) * self.speed
    self.delta.y = math.sin(self.angle) * self.speed
    Agent.move(self)
end

function Bug:chase(target)
    self.target=target   
end

```

your event class is similar to the one i use. mng:on() and mng:trigger() are lke suscribe and dispatch. Mine is the result of various improvements by several people on the forum, and i’ve used it a lot with 100% satisfaction, so i’ll stick to it.
Concerning extending a class (as i do) or subclassing (as you do) the result is exactly the same. My method (which is really to be credited to @toadkick) is maybe a bit more versatile, because my scene class is derived from a panel class, and i then add the event methods to it with extend(). Codea class() does not accept several superclasses in the definition.

It’s now on Codea Community too.

@Jmv38 yeah, I’ll have to experiment with extending the table. I can see that it allows more flexibility. Although I do like the elegance of events being restricted to a given scope. Sometimes restrictions can be helpful!