Is abstraction "good practice" in Lua?

So let’s say I have a UI module that deals with the on screen D-pad and a jump button interfacing.

Let’s also say I have a Player Character module that deals with moving the player character around the screen and holds it’s physics variables/constants.

And lastly I have Game Play Scene module that handles “onEnterFrameEvent”  Which is the best “lua / Corona” way to deal with this event?

Below is the code, but Method 1 essentially uses “public” tables that anything can modify

Method 2 uses function calls to return a bool.  Same functionality.  The tutorials I’ve watched seem to lean toward the tables, but my experience is abstraction is the best policy.  Your input please.

EDIT:  This example is simplified a bit so I don’t have to post every function, you can imply what it does by its name I think.

Method 1:

local function onEnterFrameEvent(event) local dt = dtk:getDeltaTime() if ui.dPadActive == true then player:move(ui.isPressing\_Up, ui.isPressing\_Right, ui.isPressing\_Down, ui.isPressing\_Left, dt) end if ui.isJumping == true then player:jump(dt) ui.isJumping = false end map.updateView() -- Updates a map's camera system and culling. Should be called each frame and directly after you manually move the map. end

or Method 2:

local function onEnterFrameEvent(event) local dt = dtk:getDeltaTime() if ui:get\_dPadActive() == true then -- functions returns bool player:move(ui:get\_isPressing\_Up(), ui:get\_isPressing\_Right(), ui:get\_isPressing\_Down(), ui:get\_isPressing\_Left(), dt) end if ui:get\_isJumping() == true then -- function returns bool player:jump(dt) -- setting the "isJumping" value is reset in the function call if its value was true end map.updateView() -- Updates a map's camera system and culling. Should be called each frame and directly after you manually move the map. end

Personally, I would keep things as clean as possible in your game loop and move the responsibility to the other classes.  Assuming your UI class has global scope then your player move event can easily look up state for itself and adapt accordingly (saves passing parameters around) and makes cleaner looking code.

local function onEnterFrameEvent(event) player:move() map.updateView() end

I would also move the player jumping logic into player move as this is more logical and keeps all player related stuff in player.lua.

I would not use lookup functions for simple properties as it involves writing more code and more CPU cycles to achieve the same thing.

So using

ui.isPressing\_Up

instead of

ui:get\_isPressing\_Up()

is both more efficient and cleaner more readable code

There is a third option and one I favor.  Event driven interactions.

For example, my interface objects live in their own modules and I call a builder function in that module to create them.  However, after that, the counters, dials, and other HUDs listen for events.

This is a rudimentary example to give the idea:

-- Just the concept... local counterM = {} function counterM.create( group, x, y, tag ) local counter = display.newText( group, 0, x, y, native.systemFont, 16 ) counter.curVal= 0 function counter.onCount( self, event ) if( not tag == event.tag ) then return end self.curVal = self.curVal + event.value self.text = self.curVal end; Runtime:addEventListener( "onCount", counter ) function counter.resetCount( self, event ) if( not tag == event.tag ) then return end self.curVal = 0 self.text = self.curVal end; Runtime:addEventListener( "resetCount", counter ) -- Automatically stop listening for events when destroyed function counter.finalize( self ) Runtime:removeEventListener( "onScore", self ) Runtime:removeEventListener( "resetCount", self ) end; counter:addEventListener( "finalize" ) return counter end return counterM

Now, I have a basic module that can create any number of independent counters that listen for their own events.

local counterM = require "counterM" counterM.create( display.currentStage, 100, 100, "lives" ) counterM.create( display.currentStage, 300, 100, "score" )

Later, I can do this to increment the counters:

Runtime:dispatchEvent( { name = "onCount", value = 10, tag = "score" } ) ... some time later Runtime:dispatchEvent( { name = "onCount", value = 3, tag = "lives" } ) ... some time later Runtime:dispatchEvent( { name = "onCount", value = -1, tag = "lives" } ) .. etc.

Note: While I introduced the concept of a ‘tag’ to differentiate events, in reality I use unique event names to reduce the number of objects receiving the event.  

Either will work but I prefer unique event names method over tagged events.  

However, showing the unique event name version was a bit more complex.  So, I skipped it.

RG… (Can I call you RG?)

I’m almost salivating at the ideas materializing from your reply and the almost mirror image of what 

With embedded stuff it’s common to setup interrupts based off of timers, counters, pin changes (transitions from high to low, low to high), registers and bits in registers (flags) being set/cleared based off of data being received into or transmitted from a buffer, etc… etc…

The ISR (Interrupt Service Routine) gets called when the interrupt (or EVENT) occurs.  So it’s very similar if not identical to creating timer/counter interrupts.  Why I didn’t see the relationship is slightly humiliating.  

  this sort of system (an “event bus” or “message queue” or “messenger service”, or formally the “publish-subscribe” pattern aka “pub-sub”) is quite common in game development, the primary advantage being a decoupling between sender/receiver - ie, neither needs a direct reference to the other, just a “registration” with the common messaging system.  (which then meshes nicely if you need to do networked multiplayer fe)

  Corona’s Runtime can be used (as RG showed) as a single common “master bus” for your events as well as Corona’s own internals, though it’s not very “discriminating”  - with just the one bus, each message is essentially a “broadcast” to all who’ve subscribed (a problem that RG may have been addressing with his “tags”, I think).  that’s not isolated to Corona tho, Dojo’s “topic” too, for example, suggests naming topics like “category/event” and such, which helps, but unfortunately you can’t wildcard to subscribe to fe “category/*”, so similar problem…

  consider the case of a networked game where you’ve got things to message internally and things to message externally - it might help to have separate busses.   it’s entirely feasible to roll your own with separate bus/category/subcategory/specific-event if you need all that sort of flexibility (ie, subscribe to an entire bus/queue, subscribe to whole category, or just a subcategory, or just a single specific event, etc) or to whatever degree of complexity your needs dictate.  fwiw, hth

My RL name is Ed.  ‘roaminggamer’ is my company and signature… I know.  I’m confusing that way.  So, @roaminggamer or @Ed is fine  I respond to both.

Ed you nailed it.  

I created several Runtime events in my “player” module.

I passed the event.target (“player” module), and event.names to my “ui” module.

Anywhere there was a table property for “M.isMovingLeft = true” or “M.isJumping = true” has been replaced by a dispatchEvent and adding a unamed property to the event table.

It looks something like this in the UI module:

local function onButtonTap( event ) local taps = event.numTaps -- eventual use for double jump or some nifty mechanic local targetID = event.target.id if "jump" == targetID then Runtime:dispatchEvent({name = M.jumpEvent, target=uiTarget, true}) debugString = "Jump" end end

It looks something like this in the “player” module:

local function onUserInput(event) local name = event.name local val = event[1] if(name == "playerIsJumping") then player.isJumping = val elseif(name == "playerIsMovingRight") then player.isMovingRight = val elseif(name == "playerIsMovingLeft") then player.isMovingLeft = val elseif(name == "playerIsMovingUp") then player.isMovingUp = val elseif(name == "playerIsMovingDown") then player.isMovingDown = val end end

and the “GamePlayScene” moldule, where the onEnterFrameEvent is stored.  Which is nice because the dispatched events in the UI module effect the player module.  No middle-man.  The onEnterFrameEvent just gets to call appropriate methods without a sea of parameters as they are internal to the “player” module now instead of the “ui” module.

 player:setDeltaTime(dtk:getDeltaTime()) player:move() player:jump() map.updateView() -- Updates a map's camera system and culling. Should be called each frame and directly after you manually move the map.

Thumbs up or down Ed?

I’m going to say, thumb in the middle.

I give it a thumbs up because I do use a similar technique in my games (SSK2 has a whole set of modules [‘easy inputs’] that create inputs objects to throw standard events that the player, etc can listen for).

However, I’m going to give it a thumbs down because I see some places where it could be made a little cleaner.

a. Tap versus Touch - I’m a huge fan of the touch event and not a fan of tap events.

b. Shared listener - I can see your tap listener is not shared.  Sharing it would make it easier to maintain. (See below for example)

c. Listener is Runtime - I try to make all of my (touch, collision, etc) listeners ‘table listeners’.  (See example below).

d. String comparisons - In my event system I tend to make my custom listeners for one specific event with modifying a parameters. 

EX:  ‘onJoystick’ - Has these and other parameters, vx, vy, state, time, percent, …

Your code looks like it is checking for a number of different events in one listener: ‘playerIsJumping’, ‘playerIsMovingRight’, … 

I’m not saying this is necessarily wrong, but I don’t think I’d do it quite this way.

Shared Listener + Table Listener Example

This example module will create a basic button where each button throws a custom event when tapped (yes, using tap listener since you are):

-- May contain typos local sample = {} local function onTap( self, event ) local custom = {} custom.name = self.myEvent custom.taps = event.numTaps custom.time = event.time Runtime:dispatchEvent( custom ) end sample.create( group, x, y, w, y, text, eventName ) group = group or display.currentStage local button = display.newRect( group, x, y, w, h ) button:setFillColor( 0.25, 0.25, 0.25 ) button.label = display.newText( group, text, x, y ) button.myEvent = eventName button.tap = onTap button:addEventListener( "tap" ) return button end return sample

Now you can make buttons like this, each with their own custom named events, but all using the same listener event:

local sample = require "sample" -- Assumes I saved the module in sample.lua sample.create( nil, display.contentCenterX, display.contentCenterY - 200, 200, 30, "Call Bob", "onBob" ) sample.create( nil, display.contentCenterX, display.contentCenterY, 200, 30, "Call Bill", "onBill" ) sample.create( nil, display.contentCenterX, display.contentCenterY + 200, 200, 30, "Call Sue", "onSue" ) -- Now, you can create objects that listen for one or more of these events.

One more post coming…

I also wanted to post something about complex player control, but not in the last post or it would be too long.

I see you’re trying to put together a player control system.  Now, normally, when my player uses physics and has event a moderately complex movement style, I use this practice:

  1. Set fields/properties on the player to default values when I create it.  These properties are used by subsequent logic to decide how the player is moving.

  2. Add listener(s) to the player that will modify one or more of these fields as the result of an touch, swipe, button tap, etc.

  3. Add an enterFrame listener to the player that adjusts show the player is moving from frame to frame based on changes to the aforementioned fields.

-Ed

One more note.  Why Table listeners versus Functionlisteners?

Here is one of many reasons.  Table listeners are self cleaning.

You have to remove this on your own later:

local mRand = math.random local obj = display.newCircle( 10, 10, 10 ) local function onTap( event ) obj:setFillColor( mRand(),mRand(),mRand() ) end Runtime:addEventListener( "tap", onTap ) ... later display.remove( obj ) Runtime:removeEventListener( "tap", onTap )

This is automatically removed.

local mRand = math.random local obj = display.newCircle( 10, 10, 10 ) local function onTap( self, event ) self:setFillColor( mRand(),mRand(),mRand() ) end obj.tap = onTap obj:addEventListener( "tap" ) ... later display.remove( obj )

Note: Custom event listeners, as we’ve been setting them up above are only Runtime listeners. So you still have to clean them up.  

I wanted to quote several messages.  

  1.  I started with tap because it had a “numTaps” property I think.  I thought that might be fun to play with for double jumps or maybe some twitchy mechanic for dodging bullets.  it’s doesn’t have the other phases a touch event does.  So I’ll concede on your touch > tap.

  2.  I’m still learning “scope” in Lua.  So I’m still treating things in a C-ish Fashion.  Each “event” has its own listener.  I’ll show you my methodology.  I didn’t know if it was a “good idea”, but it did work.

The “player” module has these eventListerners:

Runtime:addEventListener("playerIsJumping", onUserInput) Runtime:addEventListener("playerIsMovingRight", onUserInput) Runtime:addEventListener("playerIsMovingLeft", onUserInput) Runtime:addEventListener("playerIsMovingUp", onUserInput) Runtime:addEventListener("playerIsMovingDown", onUserInput)

In the “game play scene” module I pass them to the “ui” module:

ui:setTargetForUI(player, "playerIsJumping", "playerIsMovingRight", "playerIsMovingLeft", "playerIsMovingUp", "playerIsMovingDown")

in the “ui” module that function looks like"

function M:setTargetForUI(t, jump, right, left, up, down) uiTarget = t jumpEvent = jump rightEvent = right leftEvent = left upEvent = up downEvent = down end

and those locals on the left of = are used in the various dispatchEvent functions:

 if movement\_X ~= 0 then -- If the player is moving along X - determine direction. if movement\_X \> 0 then Runtime:dispatchEvent({name = rightEvent, target=uiTarget, true}) else Runtime:dispatchEvent({name = leftEvent, target=uiTarget, true}) end end if movement\_Y ~= 0 then -- If the player is moving along Y - determine direction. if movement\_Y \> 0 then Runtime:dispatchEvent({name = downEvent, target=uiTarget, true}) else Runtime:dispatchEvent({name = upEvent, target=uiTarget, true}) end end if movement\_X == 0 then -- If the player is not moving along X - reset left and right. Runtime:dispatchEvent({name = leftEvent, target=uiTarget, false}) Runtime:dispatchEvent({name = rightEvent, target=uiTarget, false}) end if movement\_Y == 0 then -- If the player is not moving along Y - reset up and down. Runtime:dispatchEvent({name = upEvent, target=uiTarget, false}) Runtime:dispatchEvent({name = downEvent, target=uiTarget, false}) end

I don’t know if it’s good, but it did get rid of the arguments in the enterFrame event that someone somewhere in another thread I started said it should be cleaner.

I do like your table listeners example.  I’ll adapt my code to suit.  This while process has been extremely iterative, and each iteration is a little bit better than the last.  You have been a huge boon for my understanding of Corona and migrating to Lua.

Side note below!

My youngest daughter has Cerebral Palsy.  She’s only 2.  But somehow, somewhere, someway she’s picked up “I can’t” when she tries to do stuff.  It’s soul crushing for me as a parent because I know she can and I try to encourage her every day, all day.  I want to empower her.

So my motivation is to create a game for special needs kids.  Many may not be able to play “conventional” video games.  

Something that includes a way to add a slider in the settings on how much help the player may need.  

  •  Pausing before critical collisions or events.

  •  Slowing down “time” during battles when “bullets” get within a certain distance threshold

  •  Custom placement of the UI controls

  •  Whatever else I can think of to help.

So I decided that I simply must make a platform game called “Able Allison: ‘Ican’ Champion” or something similar.

Sorry to hear about your youngest, they can be bad enough at 2 at the best of times!  Sounds like great motivation to create a game to me.

Now onto Corona, I’m a great believer in KISS. Adding complexity when a project is small really doesn’t scale - it’s fine for hundreds of lines of code, maybe even thousands but it becomes an absolute nightmare at tens of thousands!  

Here is a variation that is much simpler. I assume you will have touch points on your UI to handle player input so we will point them to a single tap handler.  

local function onTap(event)   --which button was pressed?   if event.target.id == "leftButton" then     player:setDirection("left")   elseif event.target.id == "rightButton" then     player:setDirection("right")  end end

Let’s assume you also have a player class and that has a single public event (to set the desired direction) and an enter frame (to do the movement)

function player:setDirection(direction)   if direction == "left" then     self.moveX = -1   elseif direction == "right" then     self.moveX = 1   end end local function player:onEnterFrame(event)   --now move player   self.x = self.x + self.moveX end 

This assumes the player will continually move in a single direction based on the UI object tapped.  Would be simple to turn into touch and when the touch ended call player:setDirection(“none”) to stop movement  You could then extend this by setting a deltaX value and have that decay over time to provide a bit of momentum to make the movement more natural.

Full message queue implementations have their place - massively networked games like CoD for example.  But IMHO they are overkill for most Corona games and you can end up getting bogged down in the details and never shipping your project.

Adrian, thanks I appreciate it.  She’s a trooper.  She’s verbal and has total cognitive capacity.  Just a little slow in the mobility department.

I think I’m implementing something like you describe except I’m using 5 events to to do it.  The different events all call the same function, and the dispatched events all get called from related events in the UI (The touch events, key events).

So I should whittle those 5 events down to 1.  I think a bit of my “complexity” is to test the elements I’m picking up here and when it works I add more to test if it was a fluke.  

Thanks again Adrian.  I’m modifying code now.

Personally, I would keep things as clean as possible in your game loop and move the responsibility to the other classes.  Assuming your UI class has global scope then your player move event can easily look up state for itself and adapt accordingly (saves passing parameters around) and makes cleaner looking code.

local function onEnterFrameEvent(event) player:move() map.updateView() end

I would also move the player jumping logic into player move as this is more logical and keeps all player related stuff in player.lua.

I would not use lookup functions for simple properties as it involves writing more code and more CPU cycles to achieve the same thing.

So using

ui.isPressing\_Up

instead of

ui:get\_isPressing\_Up()

is both more efficient and cleaner more readable code

There is a third option and one I favor.  Event driven interactions.

For example, my interface objects live in their own modules and I call a builder function in that module to create them.  However, after that, the counters, dials, and other HUDs listen for events.

This is a rudimentary example to give the idea:

-- Just the concept... local counterM = {} function counterM.create( group, x, y, tag ) local counter = display.newText( group, 0, x, y, native.systemFont, 16 ) counter.curVal= 0 function counter.onCount( self, event ) if( not tag == event.tag ) then return end self.curVal = self.curVal + event.value self.text = self.curVal end; Runtime:addEventListener( "onCount", counter ) function counter.resetCount( self, event ) if( not tag == event.tag ) then return end self.curVal = 0 self.text = self.curVal end; Runtime:addEventListener( "resetCount", counter ) -- Automatically stop listening for events when destroyed function counter.finalize( self ) Runtime:removeEventListener( "onScore", self ) Runtime:removeEventListener( "resetCount", self ) end; counter:addEventListener( "finalize" ) return counter end return counterM

Now, I have a basic module that can create any number of independent counters that listen for their own events.

local counterM = require "counterM" counterM.create( display.currentStage, 100, 100, "lives" ) counterM.create( display.currentStage, 300, 100, "score" )

Later, I can do this to increment the counters:

Runtime:dispatchEvent( { name = "onCount", value = 10, tag = "score" } ) ... some time later Runtime:dispatchEvent( { name = "onCount", value = 3, tag = "lives" } ) ... some time later Runtime:dispatchEvent( { name = "onCount", value = -1, tag = "lives" } ) .. etc.

Note: While I introduced the concept of a ‘tag’ to differentiate events, in reality I use unique event names to reduce the number of objects receiving the event.  

Either will work but I prefer unique event names method over tagged events.  

However, showing the unique event name version was a bit more complex.  So, I skipped it.

RG… (Can I call you RG?)

I’m almost salivating at the ideas materializing from your reply and the almost mirror image of what 

With embedded stuff it’s common to setup interrupts based off of timers, counters, pin changes (transitions from high to low, low to high), registers and bits in registers (flags) being set/cleared based off of data being received into or transmitted from a buffer, etc… etc…

The ISR (Interrupt Service Routine) gets called when the interrupt (or EVENT) occurs.  So it’s very similar if not identical to creating timer/counter interrupts.  Why I didn’t see the relationship is slightly humiliating.  

  this sort of system (an “event bus” or “message queue” or “messenger service”, or formally the “publish-subscribe” pattern aka “pub-sub”) is quite common in game development, the primary advantage being a decoupling between sender/receiver - ie, neither needs a direct reference to the other, just a “registration” with the common messaging system.  (which then meshes nicely if you need to do networked multiplayer fe)

  Corona’s Runtime can be used (as RG showed) as a single common “master bus” for your events as well as Corona’s own internals, though it’s not very “discriminating”  - with just the one bus, each message is essentially a “broadcast” to all who’ve subscribed (a problem that RG may have been addressing with his “tags”, I think).  that’s not isolated to Corona tho, Dojo’s “topic” too, for example, suggests naming topics like “category/event” and such, which helps, but unfortunately you can’t wildcard to subscribe to fe “category/*”, so similar problem…

  consider the case of a networked game where you’ve got things to message internally and things to message externally - it might help to have separate busses.   it’s entirely feasible to roll your own with separate bus/category/subcategory/specific-event if you need all that sort of flexibility (ie, subscribe to an entire bus/queue, subscribe to whole category, or just a subcategory, or just a single specific event, etc) or to whatever degree of complexity your needs dictate.  fwiw, hth

My RL name is Ed.  ‘roaminggamer’ is my company and signature… I know.  I’m confusing that way.  So, @roaminggamer or @Ed is fine  I respond to both.